Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -75,13 +75,13 @@ zFilename = &zFilename[i+1]; i = -1; } } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ - zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); + zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); }else{ - zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); + zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } @ @

%h(zFilename) @ [download]
if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -321,18 +321,18 @@ style_submenu_element("All", "All", "brlist?all"); } login_anonymous_available(); style_sidebox_begin("Nomenclature:", "33%"); @

    - @
  1. An
  2. An is a branch that has one or - @ more open leaves. + @ more %z(href("leaves"))open leaves. @ The presence of open leaves presumably means @ that the branch is still being extended with new check-ins.
  3. - @
  4. A
  5. A is a branch with only - @
    + @ . @ Closed branches are fixed and do not change (unless they are first @ reopened)
  6. @
style_sidebox_end(); @@ -356,14 +356,12 @@ } if( colorTest ){ const char *zColor = hash_color(zBr); @
  • @ %h(zBr) → %s(zColor)
  • - }else if( g.perm.History ){ - @
  • %h(zBr)
  • }else{ - @
  • %h(zBr)
  • + @
  • %z(href("%R/timeline?r=%T",zBr))%h(zBr)
  • } } if( cnt ){ @ } @@ -382,11 +380,11 @@ ** the timeline of a "brlist" page. Add some additional hyperlinks ** to the end of the line. */ static void brtimeline_extra(int rid){ Stmt q; - if( !g.perm.History ) return; + if( !g.perm.Hyperlink ) return; db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagxref.rid=%d" " AND tagxref.tagid=tag.tagid" " AND tagxref.tagtype>0" @@ -393,11 +391,11 @@ " AND tag.tagname GLOB 'sym-*'", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ [timeline] + @ %z(href("%R/timeline?r=%T",zTagName))[timeline] } db_finalize(&q); } /* Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -77,17 +77,19 @@ int i, j; char *zSep = ""; for(i=0; zPath[i]; i=j){ for(j=i; zPath[j] && zPath[j]!='/'; j++){} - if( zPath[j] && g.perm.History ){ + if( zPath[j] && g.perm.Hyperlink ){ if( zCI ){ - blob_appendf(pOut, "%s%#h", - zSep, g.zTop, zCI, j, zPath, j-i, &zPath[i]); + char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath); + blob_appendf(pOut, "%s%z%#h", + zSep, zLink, j-i, &zPath[i]); }else{ - blob_appendf(pOut, "%s%#h", - zSep, g.zTop, j, zPath, j-i, &zPath[i]); + char *zLink = href("%R/dir?name=%#T", j, zPath); + blob_appendf(pOut, "%s%z%#h", + zSep, zLink, j-i, &zPath[i]); } }else{ blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); } zSep = "/"; @@ -118,11 +120,11 @@ Blob dirname; Manifest *pM = 0; const char *zSubdirLink; login_check_credentials(); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } style_header("File List"); sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); @@ -155,39 +157,38 @@ } if( zCI ){ char zShort[20]; memcpy(zShort, zUuid, 10); zShort[10] = 0; - @

    Files of check-in [%s(zShort)] + @

    Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)] @ %s(blob_str(&dirname))

    - zSubdirLink = mprintf("%s/dir?ci=%S&name=%T", g.zTop, zUuid, zPrefix); + zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix); if( zD ){ - style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid); - style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); + style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid); + style_submenu_element("All", "All", "%R/dir?name=%t", zD); }else{ - style_submenu_element("All", "All", "%s/dir", g.zTop); + style_submenu_element("All", "All", "%R/dir"); } }else{ int hasTrunk; @

    The union of all files from all check-ins @ %s(blob_str(&dirname))

    hasTrunk = db_exists( "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'", TAG_BRANCH); - zSubdirLink = mprintf("%s/dir?name=%T", g.zTop, zPrefix); + zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); if( zD ){ - style_submenu_element("Top", "Top", "%s/dir", g.zTop); - style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", - g.zTop, zD); + style_submenu_element("Top", "Top", "%R/dir"); + style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD); if( hasTrunk ){ - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", - g.zTop,zD); + style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk", + zD); } }else{ - style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zTop); + style_submenu_element("Tip", "Tip", "%R/dir?ci=tip"); if( hasTrunk ){ - style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zTop); + style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk"); } } } /* Compute the temporary table "localfiles" containing the names @@ -278,19 +279,19 @@ } i++; zFN = db_column_text(&q, 0); if( zFN[0]=='/' ){ zFN++; - @
  • %h(zFN)/
  • + @
  • %z(href("%s%T",zSubdirLink,zFN))%h(zFN)
  • }else if( zCI ){ const char *zUuid = db_column_text(&q, 1); - @
  • %h(zFN)
  • + @
  • %z(href("%R/artifact/%s",zUuid))%h(zFN)
  • }else{ - @
  • %h(zFN) + @
  • %z(href("%R/finfo?name=%T%T",zPrefix,zFN))%h(zFN) @
  • } } db_finalize(&q); manifest_destroy(pM); @ style_footer(); } Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -1778,12 +1778,12 @@ const char *zUuid = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); if( webLabel ){ zLabel = mprintf( - "%.10s %s %13.13s", - g.zTop, zUuid, zUuid, zDate, zUser + "%.10s %s %13.13s", + zUuid, zUuid, zDate, zUser ); }else{ zLabel = mprintf("%.10s %s %13.13s", zUuid, zDate, zUser); } p->nVers++; @@ -1821,11 +1821,11 @@ if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){ fossil_redirect_home(); } style_header("File Annotation"); if( P("filevers") ) annFlags |= ANN_FILE_VERS; - annotate_file(&ann, fnid, mid, g.perm.History, iLimit, annFlags); + annotate_file(&ann, fnid, mid, g.perm.Hyperlink, iLimit, annFlags); if( P("log") ){ int i; @

    Versions analyzed:

    @
      for(i=0; i%s(zShort)] - }else{ - @ [%s(zShort)] - } + @ [%z(href("%R/event/%s",zEventId))%S(zEventId)] free(zEventId); } /* ** WEBPAGE: event @@ -130,47 +123,47 @@ g.zTop, zEventId); } zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); style_submenu_element("Context", "Context", "%s/timeline?c=%T", g.zTop, zETime); - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( showDetail ){ - style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", + style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", g.zTop, zEventId, zUuid); if( nextRid ){ char *zNext; zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); style_submenu_element("Next", "Next", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zNext); free(zNext); } if( prevRid ){ char *zPrev; zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); style_submenu_element("Prev", "Prev", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zPrev); free(zPrev); } }else{ style_submenu_element("Detail", "Detail", - "%s/event?name=%s&aid=%s&detail=1", + "%s/event?name=%s&aid=%s&detail=1", g.zTop, zEventId, zUuid); } } - if( showDetail && g.perm.History ){ + if( showDetail && g.perm.Hyperlink ){ int i; const char *zClr = 0; Blob comment; zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); - @

      Event [%S(zUuid)] at - @ [%s(zETime)] + @

      Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)] at + @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)] @ entered by user %h(pEvent->zUser) on - @ [%s(zATime)]:

      + @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)]:

      @
      for(i=0; inTag; i++){ if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ zClr = pEvent->aTag[i].zValue; } Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -305,39 +305,34 @@ @ } memcpy(zTime, &zDate[11], 5); zTime[5] = 0; @ - @ %s(zTime) + @ %z(href("%R/timeline?c=%t",zDate))%s(zTime) @
      if( zBgClr && zBgClr[0] ){ @ }else{ @ } sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); if( zUuid ){ - if( g.perm.History ){ - @ [%S(zUuid)] - }else{ - @ [%S(zUuid)] - } - @ part of check-in + @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)] part of check-in }else{ @ Deleted by check-in } hyperlink_to_uuid(zShortCkin); @ %h(zCom) (user: hyperlink_to_user(zUser, zDate, ""); @ branch: %h(zBr)) - if( g.perm.History && zUuid ){ + if( g.perm.Hyperlink && zUuid ){ const char *z = zFilename; if( fpid ){ - @ [diff] + @ %z(href("%R/fdiff?v1=%s&v2=%s",zPUuid,zUuid))[diff] } - @ + @ %z(href("%R/annotate?checkin=%S&filename=%h",zCkin,z)) @ [annotate] } @ } db_finalize(&q); Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -261,11 +261,11 @@ @ propagates to descendants } #if 0 if( zValue && fossil_strcmp(zTagname,"branch")==0 ){ @    - @ branch timeline + @ %z(href("%R/timeline?r=%T",zValue))branch timeline } #endif } if( zSrcUuid && zSrcUuid[0] ){ if( tagtype==0 ){ @@ -333,11 +333,11 @@ const char *zNew, /* blob.uuid after change. NULL for deletes */ const char *zOldName, /* Prior name. NULL if no name change. */ int diffFlags, /* Flags for text_diff(). Zero to omit diffs */ int mperm /* executable or symlink permission for zNew */ ){ - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ if( zNew==0 ){ @

      Deleted %h(zName)

      }else if( zOld==0 ){ @

      Added %h(zName)

      }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ @@ -354,35 +354,35 @@ @ } }else{ if( zOld && zNew ){ if( fossil_strcmp(zOld, zNew)!=0 ){ - @

      Modified %h(zName) - @ from [%S(zOld)] - @ to [%S(zNew)]. + @

      Modified %z(href("%R/finfo?name=%T",zName))%h(zName) + @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)] + @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)]. }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){ @

      Name change from - @ from %h(zOldName) - @ to %h(zName). + @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName) + @ to %z(href("%R/finfo?name=%T",zName))%h(zName). }else{ @

      Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for - @ %h(zName) + @ %z(href("%R/finfo?name=%T",zName))%h(zName) } }else if( zOld ){ - @

      Deleted %h(zName) - @ version [%S(zOld)] + @

      Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName) + @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)] }else{ - @

      Added %h(zName) - @ version [%S(zNew)] + @

      Added %z(href("%R/finfo?name=%T",zName))%h(zName) + @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)] } if( diffFlags ){ @

             append_diff(zOld, zNew, diffFlags);
             @ 
      }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){ @    - @ [diff] + @ %z(href("%R/fdiff?v1=%S&v2=%S",zOld,zNew))[diff] } @

      } } @@ -535,47 +535,47 @@ @ Received From: @ %h(zUser) @ %h(zIpAddr) on %s(zDate) } db_finalize(&q); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ const char *zProjName = db_get("project-name", "unnamed"); @ Timelines: - @ family + @ %z(href("%R/timeline?f=%S",zUuid))family if( zParent ){ - @ | ancestors + @ | %z(href("%R/timeline?p=%S",zUuid))ancestors } if( !isLeaf ){ - @ | descendants + @ | %z(href("%R/timeline?d=%S",zUuid))descendants } if( zParent && !isLeaf ){ - @ | both + @ | %z(href("%R/timeline?dp=%S",zUuid))both } db_prepare(&q, "SELECT substr(tag.tagname,5) FROM tagxref, tag " " WHERE rid=%d AND tagtype>0 " " AND tag.tagid=tagxref.tagid " " AND +tag.tagname GLOB 'sym-*'", rid); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ | %h(zTagName) + @ | %z(href("%R/timeline?r=%T",zTagName))%h(zTagName) } db_finalize(&q); @ @ Other Links: @ - @ files + @ %z(href("%R/dir?ci=%S",zUuid))files if( g.perm.Zip ){ - char *zUrl = mprintf("%s/tarball/%s-%S.tar.gz?uuid=%s", - g.zTop, zProjName, zUuid, zUuid); - @ | Tarball - @ | + char *zUrl = mprintf("%R/tarball/%s-%S.tar.gz?uuid=%s", + zProjName, zUuid, zUuid); + @ | %z(href("%s",zUrl))Tarball + @ | %z(href("%R/zip/%s-%S.zip?uuid=%s",zProjName,zUuid,zUuid)) @ ZIP archive fossil_free(zUrl); } - @ | manifest + @ | %z(href("%R/artifact/%S",zUuid))manifest if( g.perm.Write ){ - @ | edit + @ | %z(href("%R/ci_edit?r=%S",zUuid))edit } @ @ } @ @@ -590,43 +590,43 @@ @ db_prepare(&q, "SELECT name," " mperm," " (SELECT uuid FROM blob WHERE rid=mlink.pid)," @@ -698,15 +698,15 @@ if( g.perm.Setup ){ @ Record ID:%d(rid) } @ Original User: hyperlink_to_user(zUser, zDate, ""); - if( g.perm.History ){ + if( g.perm.Hyperlink ){ @ Commands: @ - @ history - @ | raw-text + @ &z(href("%R/whistory?name=%t",zName))history + @ | %z(href("%R/artifact/%S",zUuid))raw-text @ @ } @

      }else{ @@ -792,11 +792,11 @@ } /* ** WEBPAGE: vdiff -** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN +** URL: /vdiff?from=UUID&to=UUID&detail=BOOLEAN;sbs=BOOLEAN ** ** Show all differences between two checkins. */ void vdiff_page(void){ int ridFrom, ridTo; @@ -936,34 +936,26 @@ }else if( mPerm==PERM_EXE ){ @
    1. Executable file }else{ @
    2. File } - if( g.perm.History ){ - @ %h(zName) - }else{ - @ %h(zName) - } + @ %z(href("%R/finfo?name=%T",zName))%h(zName) @
        prevName = fossil_strdup(zName); } @
      • hyperlink_to_date(zDate,""); @ - part of checkin hyperlink_to_uuid(zVers); if( zBr && zBr[0] ){ - if( g.perm.History ){ - @ on branch %h(zBr) - }else{ - @ on branch %h(zBr) - } + @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr) } @ - %w(zCom) (user: hyperlink_to_user(zUser,zDate,""); @ ) - if( g.perm.History ){ - @ + if( g.perm.Hyperlink ){ + @ %z(href("%R/annotate?checkin=%S&filename=%T",zVers,zName)) @ [annotate] } cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_append(pDownloadName, zName, -1); @@ -989,16 +981,11 @@ if( cnt>0 ){ @ Also wiki page }else{ @ Wiki page } - if( g.perm.History ){ - @ [%h(zPagename)] - }else{ - @ [%h(zPagename)] - } - @ by + @ [%z(href("%R/wiki?name=%t",zPagename))%h(zPagename)] by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate,"."); nWiki++; cnt++; if( pDownloadName && blob_size(pDownloadName)==0 ){ @@ -1065,18 +1052,18 @@ @ Also attachment "%h(zFilename)" to }else{ @ Attachment "%h(zFilename)" to } if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ - if( g.perm.History && g.perm.RdTkt ){ - @ ticket [%S(zTarget)] + if( g.perm.Hyperlink && g.perm.RdTkt ){ + @ ticket [%z(href("%R/tktview?name=%S",zTarget))%S(zTarget)] }else{ @ ticket [%S(zTarget)] } }else{ - if( g.perm.History && g.perm.RdWiki ){ - @ wiki page [%h(zTarget)] + if( g.perm.Hyperlink && g.perm.RdWiki ){ + @ wiki page [%z(href("%R/wiki?name=%t",zTarget)))%h(zTarget)] }else{ @ wiki page [%h(zTarget)] } } @ added by @@ -1091,12 +1078,12 @@ if( cnt==0 ){ @ Control artifact. if( pDownloadName && blob_size(pDownloadName)==0 ){ blob_appendf(pDownloadName, "%.10s.txt", zUuid); } - }else if( linkToView && g.perm.History ){ - @ [view] + }else if( linkToView && g.perm.Hyperlink ){ + @ %z(href("%R/artifact/%S",zUuid))[view] } } /* @@ -1160,18 +1147,17 @@ g.zTop, P("v1"), P("v2")); } if( P("smhdr")!=0 ){ @

        Differences From Artifact - @ [%S(zV1)] To - @ [%S(zV2)].

        + @ %z(href("%R/artifact/%S",zV1))[%S(zV1)] To + @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]. }else{ @

        Differences From - @ Artifact [%S(zV1)]:

        + @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]: object_description(v1, 0, 0); - @

        To Artifact - @ [%S(zV2)]:

        + @

        To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]:

        object_description(v2, 0, 0); } @
        @
        @ %s(blob_str(&diff)) @@ -1272,11 +1258,11 @@ if( !g.perm.Read ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1419,11 +1405,11 @@ if( !g.perm.Read ){ login_needed(); return; } if( rid==0 ) fossil_redirect_home(); if( g.perm.Admin ){ const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1478,11 +1464,11 @@ @
                 @ %h(z)
                 @ 
        } }else if( strncmp(zMime, "image/", 6)==0 ){ - @ + @ }else{ @ (file is %d(blob_size(&content)) bytes of binary data) } @
    3. } @@ -1507,11 +1493,11 @@ rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); if( g.perm.Admin ){ if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", + style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", g.zTop, zUuid); }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } @@ -1522,18 +1508,17 @@ } style_header("Ticket Change Details"); zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); memcpy(zTktName, pTktChng->zTicketUuid, 10); zTktName[10] = 0; - if( g.perm.History ){ + if( g.perm.Hyperlink ){ @

      Changes to ticket - @ %s(zTktName)

      + @ %z(href("%R/tktview/%s",pTktChng->zTicketUuid)))%s(zTktName) @ @

      By %h(pTktChng->zUser) on %s(zDate). See also: - @ artifact content, and - @ ticket - @ history

      + @ %z(href("%R/artifact/%T",zUuid))artifact content, and + @ %z(href("%R/tkthistory/%s",pTktChng->zTicketUuid))ticket history

      }else{ @

      Changes to ticket %s(zTktName)

      @ @

      By %h(pTktChng->zUser) on %s(zDate). @

      @@ -1965,11 +1950,11 @@ @ @
      blob_reset(&suffix); } @

      Make changes to attributes of check-in - @ [%s(zUuid)]:

      + @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)]:

      @
      login_insert_csrf_secret(); @ @ Index: src/json_dir.c ================================================================== --- src/json_dir.c +++ src/json_dir.c @@ -66,11 +66,11 @@ char * zUuid = NULL; char const * zCI = NULL; Manifest * pM = NULL; Stmt q = empty_Stmt; int rid = 0; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'h' permissions."); return NULL; } zCI = json_find_option_cstr("checkin",NULL,"ci" ); Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ src/json_timeline.c @@ -53,11 +53,11 @@ #if 0 /* The original timeline code does not require 'h' access, but it arguably should. For JSON mode i think one could argue that History permissions are required. */ - if(! g.perm.History && !g.perm.Read ){ + if(! g.perm.Hyperlink && !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access."); return NULL; } #endif return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); @@ -426,11 +426,11 @@ int check = 0; char showFiles = -1/*magic number*/; Stmt q = empty_Stmt; char warnRowToJsonFailed = 0; Blob sql = empty_blob; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ /* Reminder to self: HTML impl requires 'o' (Read) rights. */ json_set_err( FSL_JSON_E_DENIED, "Checkin timeline requires 'h' access." ); return NULL; Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ src/json_wiki.c @@ -492,11 +492,11 @@ Manifest * pW1 = NULL, *pW2 = NULL; Blob w1 = empty_blob, w2 = empty_blob, d = empty_blob; char const * zErrTag = NULL; int diffFlags; char * zUuid = NULL; - if( !g.perm.History ){ + if( !g.perm.Hyperlink ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'h' permissions."); return NULL; } Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -562,11 +562,11 @@ redirect_to_g(); } } style_header("Login/Logout"); @ %s(zErrMsg) - if( zGoto ){ + if( zGoto && P("anon")==0 ){ @

      A login is required for %h(zGoto).

      } @ if( zGoto ){ @ @@ -911,13 +911,15 @@ } /* Set the capabilities */ login_replace_capabilities(zCap, 0); login_set_anon_nobody_capabilities(); - if( zCap[0] && !g.perm.History && db_get_boolean("auto-enable-hyperlinks",1) + if( zCap[0] && !g.perm.Hyperlink + && db_get_boolean("auto-enable-hyperlinks",1) && isHuman(P("HTTP_USER_AGENT")) ){ - g.perm.History = 1; + g.perm.Hyperlink = 1; + g.javascriptHyperlink = 1; } /* If the public-pages glob pattern is defined and REQUEST_URI matches ** one of the globs in public-pages, then also add in all default-perms ** permissions. @@ -973,21 +975,21 @@ } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ case 's': g.perm.Setup = 1; /* Fall thru into Admin */ case 'a': g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip = - g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = - g.perm.ApndWiki = g.perm.History = g.perm.Clone = - g.perm.NewTkt = g.perm.Password = g.perm.RdAddr = - g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1; - /* Fall thru into Read/Write */ + g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki = + g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone = + g.perm.NewTkt = g.perm.Password = g.perm.RdAddr = + g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt = 1; + /* Fall thru into Read/Write */ case 'i': g.perm.Read = g.perm.Write = 1; break; case 'o': g.perm.Read = 1; break; case 'z': g.perm.Zip = 1; break; case 'd': g.perm.Delete = 1; break; - case 'h': g.perm.History = 1; break; + case 'h': g.perm.Hyperlink = 1; break; case 'g': g.perm.Clone = 1; break; case 'p': g.perm.Password = 1; break; case 'j': g.perm.RdWiki = 1; break; case 'k': g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1; break; @@ -1053,15 +1055,14 @@ case 'c': rc = g.perm.ApndTkt; break; case 'd': rc = g.perm.Delete; break; case 'e': rc = g.perm.RdAddr; break; case 'f': rc = g.perm.NewWiki; break; case 'g': rc = g.perm.Clone; break; - case 'h': rc = g.perm.History; break; + case 'h': rc = g.perm.Hyperlink; break; case 'i': rc = g.perm.Write; break; case 'j': rc = g.perm.RdWiki; break; case 'k': rc = g.perm.WrWiki; break; - /* case 'l': */ case 'm': rc = g.perm.ApndWiki; break; case 'n': rc = g.perm.NewTkt; break; case 'o': rc = g.perm.Read; break; case 'p': rc = g.perm.Password; break; /* case 'q': */ @@ -1129,23 +1130,23 @@ assert(0); } } /* -** Call this routine if the user lacks okHistory permission. If -** the anonymous user has okHistory permission, then paint a mesage +** Call this routine if the user lacks g.perm.Hyperlink permission. If +** the anonymous user has Hyperlink permission, then paint a mesage ** to inform the user that much more information is available by ** logging in as anonymous. */ void login_anonymous_available(void){ - if( !g.perm.History && + if( !g.perm.Hyperlink && db_exists("SELECT 1 FROM user" " WHERE login='anonymous'" " AND cap LIKE '%%h%%'") ){ const char *zUrl = PD("REQUEST_URI", "index"); @

      Many hyperlinks are disabled.
      - @ Use anonymous login + @ Use anonymous login @ to enable hyperlinks.

      } } /* Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -60,11 +60,11 @@ char Delete; /* d: delete wiki or tickets */ char Password; /* p: change password */ char Query; /* q: create new reports */ char Write; /* i: xfer inbound. checkin */ char Read; /* o: xfer outbound. checkout */ - char History; /* h: access historical information. */ + char Hyperlink; /* h: enable the display of hyperlinks */ char Clone; /* g: clone */ char RdWiki; /* j: view wiki via web */ char NewWiki; /* f: create new wiki via web */ char ApndWiki; /* m: append to wiki via web */ char WrWiki; /* k: edit wiki via web */ @@ -135,11 +135,12 @@ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ int clockSkewSeen; /* True if clocks on client and server out of sync */ - int isHTTP; /* True if running in server/CGI modes, else assume CLI. */ + char isHTTP; /* True if erver/CGI modes, else assume CLI. */ + char javascriptHyperlink; /* If true, set href= using script, not HTML */ int urlIsFile; /* True if a "file:" url */ int urlIsHttps; /* True if a "https:" url */ int urlIsSsh; /* True if an "ssh:" url */ char *urlName; /* Hostname for http: or filename for file: */ Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -47,10 +47,11 @@ #define etFOSSILIZE 19 /* The fossil header encoding format. */ #define etPATH 20 /* Path type */ #define etWIKISTR 21 /* Wiki text rendered from a char*: %w */ #define etWIKIBLOB 22 /* Wiki text rendered from a Blob*: %W */ #define etSTRINGID 23 /* String with length limit for a UUID prefix: %S */ +#define etROOT 24 /* String value of g.zTop: % */ /* ** An "etByte" is an 8-bit unsigned value. */ @@ -93,10 +94,11 @@ { 'b', 0, 2, etBLOB, 0, 0 }, { 'B', 0, 2, etBLOBSQL, 0, 0 }, { 'w', 0, 2, etWIKISTR, 0, 0 }, { 'W', 0, 2, etWIKIBLOB, 0, 0 }, { 'h', 0, 4, etHTMLIZE, 0, 0 }, + { 'R', 0, 0, etROOT, 0, 0 }, { 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */ { 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */ { 'F', 0, 4, etFOSSILIZE, 0, 0 }, { 'S', 0, 4, etSTRINGID, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, @@ -570,10 +572,15 @@ bufpt[i]=e[i]; } } bufpt[length]='\0'; break; + } + case etROOT: { + bufpt = g.zTop; + length = (int)strlen(bufpt); + break; } case etSTRINGID: { precision = 16; /* Fall through */ } Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -56,26 +56,29 @@ cnt++; blob_appendf(&ril, "
    4. "); if( zTitle[0] == '_' ){ blob_appendf(&ril, "%s", zTitle); } else { - blob_appendf(&ril, "%h", rn, zTitle); + blob_appendf(&ril, "%z%h", href("%R/rptview?rn=%d", rn), zTitle); } blob_appendf(&ril, "   "); if( g.perm.Write && zOwner && zOwner[0] ){ blob_appendf(&ril, "(by %h) ", zOwner); } if( g.perm.TktFmt ){ - blob_appendf(&ril, "[copy] ", rn); + blob_appendf(&ril, "[%zcopy] ", + href("%R/rptedit?rn=%d©=1", rn)); } if( g.perm.Admin || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0) ){ - blob_appendf(&ril, "[edit] ", rn); + blob_appendf(&ril, "[%zedit]", + href("%R/rptedit?rn=%d", rn)); } if( g.perm.TktFmt ){ - blob_appendf(&ril, "[sql] ", rn); + blob_appendf(&ril, "[%zsql]", + href("%R/rptsql?rn=%d", rn)); } blob_appendf(&ril, "
    5. \n"); } Th_Store("report_items", blob_str(&ril)); @@ -416,11 +419,11 @@ } } if( zOwner==0 ) zOwner = g.zLogin; style_submenu_element("Cancel", "Cancel", "reportlist"); if( rn>0 ){ - style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); + style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); } style_header(rn>0 ? "Edit Report Format":"Create New Report Format"); if( zErr ){ @
      %h(zErr)
      } @@ -720,11 +723,11 @@ if( i==pState->iBg ) continue; zData = azArg[i]; if( zData==0 ) zData = ""; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ if( zTid && g.perm.Write ){ - @ + @ zTid = 0; } if( zData[0] ){ Blob content; @ - }else{ - @ - } + @ }else if( zData[0]==0 ){ @ }else{ @ } } if( zTid && g.perm.Write ){ - @ + @ } @ return 0; } @@ -949,11 +948,11 @@ if( !tabs ){ struct GenerateHTML sState; db_multi_exec("PRAGMA empty_result_callbacks=ON"); style_submenu_element("Raw", "Raw", - "rptview?tablist=1&%h", PD("QUERY_STRING","")); + "rptview?tablist=1&%h", PD("QUERY_STRING","")); if( g.perm.Admin || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn); } if( g.perm.TktFmt ){ Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -524,11 +524,11 @@ @ %s(B('d'))Delete
      @ %s(B('e'))Email
      @ %s(B('p'))Password
      @ %s(B('i'))Check-In
      @ %s(B('o'))Check-Out
      - @ %s(B('h'))History
      + @ %s(B('h'))Hyperlinks
      @ %s(B('u'))Reader
      @ %s(B('v'))Developer
      @ %s(B('g'))Clone
      @ %s(B('j'))Read Wiki
      @ %s(B('f'))New Wiki
      @@ -625,24 +625,24 @@ @ is first posted. The Setup user can @ delete anything at any time. @

      @ @
    6. - @ The History privilege allows a user + @ The Hyperlinks privilege allows a user @ to see most hyperlinks. This is recommended ON for most logged-in users @ but OFF for user "nobody" to avoid problems with spiders trying to walk - @ every historical version of every baseline and file. + @ every diff and annotation of every historical check-in and file. @

    7. @ @
    8. @ The Zip privilege allows a user to @ see the "download as ZIP" @ hyperlink and permits access to the /zip page. This allows @ users to download ZIP archives without granting other rights like @ Read or - @ History. This privilege is recommended for - @ user nobody so that automatic package + @ Hyperlink. The "z" privilege is recommended + @ for user nobody so that automatic package @ downloaders can obtain the sources without going through the login @ procedure. @

    9. @ @
    10. @@ -704,12 +704,12 @@ @ To disable universal access to the repository, make sure no user named @ nobody exists or that the @ nobody user has no capabilities @ enabled. The password for nobody is ignore. @ To avoid problems with spiders overloading the server, it is recommended - @ that the h (History) capability be turned - @ off for the nobody user. + @ that the h (Hyperlinks) capability be + @ turned off for the nobody user. @

    11. @ @
    12. @ Login is required for user anonymous but the @ password is displayed on the login screen beside the password entry box @@ -891,19 +891,27 @@ @ than this, then the client will issue multiple HTTP requests. @ Values below 1 million are not recommended. 5 million is a @ reasonable number.

      @
      - onoff_attribute("Enable hyperlinks for \"nobody\" based on User-Agent", - "auto-enable-hyperlinks", "autohyperlink", 1); + onoff_attribute( + "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript", + "auto-enable-hyperlinks", "autohyperlink", 1); @

      Enable hyperlinks (the equivalent of the "h" permission) for all users - @ including user "nobody", as long as the User-Agent string in the HTTP header - @ indicates that the request is coming from an actual human being and not a - @ a robot or script. Note: Bots can specify whatever User-Agent string they - @ that want. So a bot that wants to impersonate a human can easily do so. - @ Hence, this technique does not necessarily exclude malicious bots. - @

      + @ including user "nobody", as long as (1) the User-Agent string in the + @ HTTP header indicates that the request is coming from an actual human + @ being and not a a robot or spider and (2) the user agent is able to + @ run Javascript in order to set the href= attribute of hyperlinks. Bots + @ and spiders can specify whatever User-Agent string they that want and + @ they can run javascript just like browsers. But most bots don't go to + @ that much trouble so this is normally an effective defense.

      + @ + @

      You do not normally want a bot to walk your entire repository because + @ if it does, your server will end up computing diffs and annotations for + @ every historical version of every file and creating ZIPs and tarballs of + @ every historical check-in, which can use a lot of CPU and bandwidth + @ even for relatively small projects.

      @
      entry_attribute("Public pages", 30, "public-pages", "pubpage", ""); @

      A comma-separated list of glob patterns for pages that are accessible Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -45,10 +45,94 @@ /* ** remember, if a sidebox was used */ static int sideboxUsed = 0; + +/* +** List of hyperlinks that need to be resolved by javascript in +** the footer. +*/ +char **aHref = 0; +int nHref = 0; +int nHrefAlloc = 0; + +/* +** Generate and return a anchor tag like this: +** +** +** or +** +** The form of the anchor tag is determined by the g.javascriptHyperlink +** variable. The href="URL" form is used if g.javascriptHyperlink is false. +** If g.javascriptHyperlink is true then the +** id="ID" form is used and javascript is generated in the footer to cause +** href values to be inserted after the page has loaded. If +** g.perm.History is false, then the form is still +** generated but the javascript is not generated so the links never +** activate. +** +** Filling in the href="URL" using javascript is a defense against bots. +** +** The name of this routine is deliberately kept short so that can be +** easily used within @-lines. Example: +** +** @ %z(href("%R/artifact/%s",zUuid))%h(zFN) +** +** Note %z format. The string returned by this function is always +** obtained from fossil_malloc() so rendering it with %z will reclaim +** that memory space. +** +** There are two versions of this routine: href() does a plain hyperlink +** and xhref() adds extra attribute text. +*/ +char *xhref(const char *zExtra, const char *zFormat, ...){ + char *zUrl; + va_list ap; + va_start(ap, zFormat); + zUrl = vmprintf(zFormat, ap); + va_end(ap); + if( g.perm.Hyperlink && !g.javascriptHyperlink ){ + return mprintf("", zExtra, zUrl); + } + if( nHref>=nHrefAlloc ){ + nHrefAlloc = nHrefAlloc*2 + 10; + aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0])); + } + aHref[nHref++] = zUrl; + return mprintf("", zExtra, nHref); +} +char *href(const char *zFormat, ...){ + char *zUrl; + va_list ap; + va_start(ap, zFormat); + zUrl = vmprintf(zFormat, ap); + va_end(ap); + if( g.perm.Hyperlink && !g.javascriptHyperlink ){ + return mprintf("", zUrl); + } + if( nHref>=nHrefAlloc ){ + nHrefAlloc = nHrefAlloc*2 + 10; + aHref = fossil_realloc(aHref, nHrefAlloc*sizeof(aHref[0])); + } + aHref[nHref++] = zUrl; + return mprintf("", nHref); +} + +/* +** Generate javascript that will set the href= attribute on all anchors. +*/ +void style_resolve_href(void){ + int i; + if( !g.perm.Hyperlink || !g.javascriptHyperlink || nHref==0 ) return; + @ +} + /* ** Add a new element to the submenu */ void style_submenu_element( const char *zLabel, @@ -164,10 +248,13 @@ if( g.thTrace ){ cgi_append_content("


      \n", -1); cgi_append_content(blob_str(&g.thLog), blob_size(&g.thLog)); cgi_append_content("\n", -1); } + + /* Set the href= field on hyperlinks */ + style_resolve_href(); } /* ** Begin a side-box on the right-hand side of a page. The title and ** the width of the box are given as arguments. The width is usually Index: src/tag.c ================================================================== --- src/tag.c +++ src/tag.c @@ -548,12 +548,12 @@ " ORDER BY tagname" ); @
        while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); - if( g.perm.History ){ - @
      • + if( g.perm.Hyperlink ){ + @
      • %z(xhref("class='taglink'","%R/timeline?t=%T",zName)) @ %h(zName)
      • }else{ @
      • %h(zName)
      • } } Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -47,37 +47,37 @@ ** Generate a hyperlink to a version. */ void hyperlink_to_uuid(const char *zUuid){ char z[UUID_SIZE+1]; shorten_uuid(z, zUuid); - if( g.perm.History ){ - @ [%s(z)] + if( g.perm.Hyperlink ){ + @ %z(xhref("class='timelineHistLink'","%R/info/%s",z))[%s(z)] }else{ @ [%s(z)] } } /* ** Generate a hyperlink to a diff between two versions. */ void hyperlink_to_diff(const char *zV1, const char *zV2){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zV2==0 ){ - @ [diff] + @ %z(href("%R/diff?v2=%s",zV1))[diff] }else{ - @ [diff] + @ %z(href("%R/diff?v1=%s&v2=%s",zV1,zV2))[diff] } } } /* ** Generate a hyperlink to a date & time. */ void hyperlink_to_date(const char *zDate, const char *zSuffix){ if( zSuffix==0 ) zSuffix = ""; - if( g.perm.History ){ - @ %s(zDate)%s(zSuffix) + if( g.perm.Hyperlink ){ + @ %z(href("%R/timeline?c=%T",zDate))%s(zDate)%s(zSuffix) }else{ @ %s(zDate)%s(zSuffix) } } @@ -86,15 +86,15 @@ ** events by that user. If the date+time is specified, then the timeline ** is centered on that date+time. */ void hyperlink_to_user(const char *zU, const char *zD, const char *zSuf){ if( zSuf==0 ) zSuf = ""; - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zD && zD[0] ){ - @ %h(zU)%s(zSuf) + @ %z(href("%R/timeline?c=%T&u=%T",zD,zU))%h(zU)%s(zSuf) }else{ - @ %h(zU)%s(zSuf) + @ %z(href("%R/timeline?u=%T",zU))%h(zU)%s(zSuf) } }else{ @ %s(zU) } } @@ -353,39 +353,37 @@ /* Generate the "user: USERNAME" at the end of the comment, together ** with a hyperlink to another timeline for that user. */ if( zTagList && zTagList[0]==0 ) zTagList = 0; - if( g.perm.History && fossil_strcmp(zUser, zThisUser)!=0 ){ - char *zLink = mprintf("%s/timeline?u=%h&c=%t&nd", - g.zTop, zUser, zDate); - @ (user: %h(zUser)%s(zTagList?",":"\051") - fossil_free(zLink); + if( g.perm.Hyperlink && fossil_strcmp(zUser, zThisUser)!=0 ){ + char *zLink = mprintf("%R/timeline?u=%h&c=%t&nd", zUser, zDate); + @ (user: %z(href("%z",zLink))%h(zUser)%s(zTagList?",":"\051") }else{ @ (user: %h(zUser)%s(zTagList?",":"\051") } /* Generate a "detail" link for tags. */ - if( zType[0]=='g' && g.perm.History ){ - @ [details] + if( zType[0]=='g' && g.perm.Hyperlink ){ + @ [%z(href("%R/info/%S",zUuid))details] } /* Generate the "tags: TAGLIST" at the end of the comment, together ** with hyperlinks to the tag list. */ if( zTagList ){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ int i; const char *z = zTagList; Blob links; blob_zero(&links); while( z && z[0] ){ for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){} if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){ blob_appendf(&links, - "%#h%.2s", - g.zTop, i, z, zDate, i, z, &z[i] + "%z%#h%.2s", + href("%R/timeline?r=%#t&nd&c=%s",i,z,zDate), i,z, &z[i] ); }else{ blob_appendf(&links, "%#h", i+2, z); } if( z[i]==0 ) break; @@ -403,11 +401,11 @@ if( xExtra ){ xExtra(rid); } /* Generate the file-change list if requested */ - if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.History ){ + if( (tmFlags & TIMELINE_FCHANGES)!=0 && zType[0]=='c' && g.perm.Hyperlink ){ int inUl = 0; if( !fchngQueryInit ){ db_prepare(&fchngQuery, "SELECT (pid==0) AS isnew," " (fid==0) AS isdel," @@ -433,26 +431,26 @@ @
          inUl = 1; } if( isNew ){ @
        • %h(zFilename) (new file)   - @ [view]
        • + @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew)) + @ [view] }else if( isDel ){ @
        • %h(zFilename) (deleted)
        • }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){ @
        • %h(zOldName) → %h(zFilename) - @ [view]
        • + @ %z(xhref("target='diffwindow'","%R/artifact/%S",zNew)) + @ [view] }else{ if( zOldName!=0 ){ @
        • %h(zOldName) → %h(zFilename) }else{ @
        • %h(zFilename)   } - @ [diff]
        • + @ %z(xhref("target='diffwindow'","%R/fdiff?v1=%S&v2=%S",zOld,zNew)) + @ [diff] } } db_reset(&fchngQuery); if( inUl ){ @
        @@ -974,21 +972,13 @@ p = p->u.pTo; } blob_append(&sql, ")", -1); path_reset(); blob_append(&desc, "All nodes on the path from ", -1); - if( g.perm.History ){ - blob_appendf(&desc, "[%h]", g.zTop,zFrom,zFrom); - }else{ - blob_appendf(&desc, "[%h]", zFrom); - } + blob_appendf(&desc, "%z%h", href("%R/info/%h", zFrom), zFrom); blob_append(&desc, " and ", -1); - if( g.perm.History ){ - blob_appendf(&desc, "[%h].", g.zTop, zTo, zTo); - }else{ - blob_appendf(&desc, "[%h].", zTo); - } + blob_appendf(&desc, "%z[%h]", href("%R/info/%h",zTo), zTo); tmFlags |= TIMELINE_DISJOINT; db_multi_exec("%s", blob_str(&sql)); }else if( (p_rid || d_rid) && g.perm.Read ){ /* If p= or d= is present, ignore all other parameters other than n= */ char *zUuid; @@ -1021,16 +1011,12 @@ blob_appendf(&desc, "%d ancestors", np); db_multi_exec("%s", blob_str(&sql)); } if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid); } - if( g.perm.History ){ - blob_appendf(&desc, " of [%.10s]", - g.zTop, zUuid, zUuid); - }else{ - blob_appendf(&desc, " of check-in [%.10s]", zUuid); - } + blob_appendf(&desc, " of %z[%.10s]", + href("%R/info/%s", zUuid), zUuid); }else if( f_rid && g.perm.Read ){ /* If f= is present, ignore all other parameters other than n= */ char *zUuid; db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" @@ -1042,16 +1028,11 @@ blob_appendf(&sql, " AND event.objid IN ok"); db_multi_exec("%s", blob_str(&sql)); if( useDividers ) timeline_add_dividers(0, f_rid); blob_appendf(&desc, "Parents and children of check-in "); zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid); - if( g.perm.History ){ - blob_appendf(&desc, "[%.10s]", - g.zTop, zUuid, zUuid); - }else{ - blob_appendf(&desc, "[%.10s]", zUuid); - } + blob_appendf(&desc, "%z[%.10s]", href("%R/info/%s", zUuid), zUuid); }else{ /* Otherwise, a timeline based on a span of time */ int n; const char *zEType = "timeline item"; char *zDate; @@ -1219,11 +1200,11 @@ blob_appendf(&desc, " occurring around %h.
        ", zCirca); } if( zSearch ){ blob_appendf(&desc, " matching \"%h\"", zSearch); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ if( zAfter || n==nEntry ){ zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/"); timeline_submenu(&url, "Older", "b", zDate, "a"); free(zDate); } @@ -1623,11 +1604,11 @@ */ void test_timewarp_page(void){ Stmt q; login_check_credentials(); - if( !g.perm.Read || !g.perm.History ){ login_needed(); return; } + if( !g.perm.Read || !g.perm.Hyperlink ){ login_needed(); return; } style_header("Instances of timewarp"); @
          db_prepare(&q, "SELECT blob.uuid " " FROM plink p, plink c, blob" @@ -1635,10 +1616,10 @@ " AND blob.rid=c.cid" ); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); @
        • - @ %S(zUuid) + @ %S(zUuid) } db_finalize(&q); style_footer(); } Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -305,11 +305,11 @@ if( !g.perm.RdTkt ){ login_needed(); return; } if( g.perm.WrTkt || g.perm.ApndTkt ){ style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T", g.zTop, PD("name","")); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ style_submenu_element("History", "History Of This Ticket", "%s/tkthistory/%T", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline Of This Ticket", "%s/tkttimeline/%T", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins Of This Ticket", @@ -319,11 +319,11 @@ style_submenu_element("New Ticket", "Create a new ticket", "%s/tktnew", g.zTop); } if( g.perm.ApndTkt && g.perm.Attach ){ style_submenu_element("Attach", "Add An Attachment", - "%s/attachadd?tkt=%T&from=%s/tktview/%t", + "%s/attachadd?tkt=%T&from=%s/tktview/%t", g.zTop, zUuid, g.zTop, zUuid); } style_header("View Ticket"); if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW
          \n", -1); ticket_init(); @@ -353,20 +353,20 @@ @

          Attachments:

          @ @@ -647,11 +647,11 @@ return 0; } /* ** WEBPAGE: tkttimeline -** URL: /tkttimeline?name=TICKETUUID&y=TYPE +** URL: /tkttimeline?name=TICKETUUID&y=TYPE ** ** Show the change history for a single ticket in timeline format. */ void tkttimeline_page(void){ Stmt q; @@ -662,16 +662,16 @@ int tagid; char zGlobPattern[50]; const char *zType; login_check_credentials(); - if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zType = PD("y","a"); if( zType[0]!='c' ){ style_submenu_element("Check-ins", "Check-ins", - "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); + "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid); }else{ style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%T", g.zTop, zUuid); } style_submenu_element("History", "History", @@ -736,17 +736,17 @@ char *zTitle; const char *zUuid; int tagid; login_check_credentials(); - if( !g.perm.History || !g.perm.RdTkt ){ login_needed(); return; } + if( !g.perm.Hyperlink || !g.perm.RdTkt ){ login_needed(); return; } zUuid = PD("name",""); zTitle = mprintf("History Of Ticket %h", zUuid); style_submenu_element("Status", "Status", "%s/info/%s", g.zTop, zUuid); style_submenu_element("Check-ins", "Check-ins", - "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); + "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid); style_submenu_element("Timeline", "Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid); style_header(zTitle); free(zTitle); @@ -786,20 +786,20 @@ @

          Delete attachment "%h(zFile)" }else{ @ @

          Add attachment "%h(zFile)" } - @ [%s(zShort)] + @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)] @ (rid %d(rid)) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, ".

          "); }else{ pTicket = manifest_get(rid, CFTYPE_TICKET); if( pTicket ){ @ @

          Ticket change - @ [%s(zShort)] + @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)] @ (rid %d(rid)) by hyperlink_to_user(pTicket->zUser,zDate," on"); hyperlink_to_date(zDate, ":"); @

          ticket_output_change_artifact(pTicket); Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -104,11 +104,11 @@ return; } style_header("Home"); @

          This is a stub home-page for the project. @ To fill in this page, first go to - @ setup/config + @ %z(href("%R/setup_config"))setup/config @ and establish a "Project Name". Then create a @ wiki page with that name. The content of that wiki page @ will be displayed in place of this message.

          style_footer(); } @@ -142,27 +142,25 @@ if( zPageName==0 ){ style_header("Wiki"); @
            { char *zHomePageName = db_get("project-name",0); if( zHomePageName ){ - @
          • + @
          • %z(href("%R/wiki?name=%t",zHomePageName)) @ %h(zHomePageName) wiki home page.
          • } } - @
          • Recent changes to wiki - @ pages.
          • - @
          • Formatting rules for - @ wiki.
          • - @
          • Use the Sandbox + @
          • %z(href("%R/timeline?y=w"))Recent changes to wiki pages.
          • + @
          • %z(href("%R/wiki_rules"))Formatting rules for wiki.
          • + @
          • Use the %z(href("%R/wiki?name=Sandbox"))Sandbox @ to experiment.
          • if( g.perm.NewWiki ){ - @
          • Create a new wiki page.
          • + @
          • Create a %z(href("%R/wikinew"))new wiki page.
          • if( g.perm.Write ){ - @
          • Create a new event.
          • + @
          • Create a %z(href("%R/eventedit"))new event.
          • } } - @
          • List of All Wiki Pages + @
          • %z(href("%R/wcontent"))List of All Wiki Pages @ available on this server.
          • @
          • @ Search wiki titles: @  
            @
          • @@ -192,18 +190,18 @@ style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", g.zTop, zPageName); } if( rid && g.perm.ApndWiki && g.perm.Attach ){ style_submenu_element("Attach", "Add An Attachment", - "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", + "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T", g.zTop, zPageName, g.zTop, zPageName); } if( rid && g.perm.ApndWiki ){ style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T", g.zTop, zPageName); } - if( g.perm.History ){ + if( g.perm.Hyperlink ){ style_submenu_element("History", "History", "%s/whistory?name=%T", g.zTop, zPageName); } } style_header(zPageName); @@ -225,20 +223,20 @@ @

            Attachments:

            @ @@ -544,11 +542,11 @@ ** Function called to output extra text at the end of each line in ** a wiki history listing. */ static void wiki_history_extra(int rid){ if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){ - @ [diff] + @ %z(href("%R/wdiff?name=%t&a=%d",zWikiPageName,rid))[diff] } } /* ** WEBPAGE: whistory @@ -560,11 +558,11 @@ Stmt q; char *zTitle; char *zSQL; const char *zPageName; login_check_credentials(); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } zPageName = PD("name",""); zTitle = mprintf("History Of %s", zPageName); style_header(zTitle); free(zTitle); @@ -597,11 +595,11 @@ Blob w1, w2, d; int diffFlags; login_check_credentials(); rid1 = atoi(PD("a","0")); - if( !g.perm.History ){ login_needed(); return; } + if( !g.perm.Hyperlink ){ login_needed(); return; } if( rid1==0 ) fossil_redirect_home(); rid2 = atoi(PD("b","0")); zPageName = PD("name",""); zTitle = mprintf("Changes To %s", zPageName); style_header(zTitle); @@ -674,13 +672,13 @@ wiki_prepare_page_list(&q); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); int size = db_column_int(&q, 1); if( size>0 ){ - @
          • %h(zName)
          • + @
          • %z(href("%R/wiki?name=%T",zName))%h(zName)
          • }else if( showAll ){ - @
          • %h(zName)
          • + @
          • %z(href("%R/wiki?name=%T",zName))%h(zName)
          • } } db_finalize(&q); @
          style_footer(); @@ -704,11 +702,11 @@ "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" " ORDER BY lower(tagname) /*sort*/" , zTitle); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); - @
        • %h(zName)
        • + @
        • %z(href("%R/wiki?name=%T",zName))%h(zName)
        • } db_finalize(&q); @
        style_footer(); } Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -1043,17 +1043,13 @@ || strncmp(zTarget, "mailto:", 7)==0 ){ blob_appendf(p->pOut, "", zTarget); /* zTerm = "⟾"; // doesn't work on windows */ }else if( zTarget[0]=='/' ){ - if( 1 /* g.perm.History */ ){ - blob_appendf(p->pOut, "", g.zTop, zTarget); - }else{ - zTerm = ""; - } + blob_appendf(p->pOut, "", g.zTop, zTarget); }else if( zTarget[0]=='.' || zTarget[0]=='#' ){ - if( 1 /* g.perm.History */ ){ + if( 1 ){ blob_appendf(p->pOut, "", zTarget); }else{ zTerm = ""; } }else if( is_valid_uuid(zTarget) ){ @@ -1061,36 +1057,34 @@ if( is_ticket(zTarget, &isClosed) ){ /* Special display processing for tickets. Display the hyperlink ** as crossed out if the ticket is closed. */ if( isClosed ){ - if( g.perm.History ){ + if( g.perm.Hyperlink ){ blob_appendf(p->pOut, - "[", - g.zTop, zTarget + "%z[", + href("%R/info/%s",zTarget) ); zTerm = "]"; }else{ blob_appendf(p->pOut,"["); zTerm = "]"; } }else{ - if( g.perm.History ){ - blob_appendf(p->pOut,"[", - g.zTop, zTarget - ); + if( g.perm.Hyperlink ){ + blob_appendf(p->pOut,"%z[", href("%R/info/%s", zTarget)); zTerm = "]"; }else{ blob_appendf(p->pOut, "["); zTerm = "]"; } } }else if( !in_this_repo(zTarget) ){ blob_appendf(p->pOut, "[", zTarget); zTerm = "]"; - }else if( g.perm.History ){ - blob_appendf(p->pOut, "[", g.zTop, zTarget); + }else if( g.perm.Hyperlink ){ + blob_appendf(p->pOut, "%z[",href("%R/info/%s", zTarget)); zTerm = "]"; } }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-' && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){ blob_appendf(p->pOut, "", g.zTop, zTarget);
    13. edit%z(href("%R/tktedit/%h",zTid))edit
      nCol)> @@ -732,25 +735,21 @@ wiki_convert(&content, 0, 0); blob_reset(&content); } }else if( azName[i][0]=='#' ){ zTid = zData; - if( g.perm.History ){ - @ %h(zData)%h(zData)%z(href("%R/tktview?name=%h",zData))%h(zData)  @ %h(zData) @ edit%z(href("%R/tktedit/%h",zTid))edit