Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -1001,14 +1001,22 @@ } /* ** WEBPAGE: rptview ** -** Generate a report. The rn query parameter is the report number -** corresponding to REPORTFMT.RN. If the tablist query parameter exists, +** Generate a report. The "rn" query parameter is the report number +** corresponding to REPORTFMT.RN. If the "tablist" query parameter exists, ** then the output consists of lines of tab-separated fields instead of ** an HTML table. +** +** Submenu of the /rptview page can be extended with additional +** hyperlinks by providing query parameter(s) of the form rvsmplXY=Z. +** Optional ending XY consists of a digit X from the set {1,2,3,4,5} +** and an optional letter Y that (if present) must be either 'a' or 's'. +** Mandatory Z is a repo-local hyperlink's target (wihout leading '/'). +** +** For details see the wiki page "branch/rptview-submenu-paralink". */ void rptview_page(void){ rptview_page_content(0, 1, 1); } @@ -1092,10 +1100,12 @@ style_submenu_element("Reports","%R/reportlist?%s",zQS); } else { style_submenu_element("Raw","%R/%s?tablist=1",g.zPath); style_submenu_element("Reports","%R/reportlist"); } + style_submenu_parametric("rv"); + if( g.perm.Admin || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ style_submenu_element("Edit", "rptedit?rn=%d", rn); } if( g.perm.TktFmt ){ Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -32,10 +32,11 @@ ** style_submenu_entry() ** style_submenu_checkbox() ** style_submenu_binary() ** style_submenu_multichoice() ** style_submenu_sql() +** style_submenu_parametric() ** ** prior to calling style_finish_page(). The style_finish_page() routine ** will generate the appropriate HTML text just below the main ** menu. */ @@ -366,10 +367,111 @@ aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL; aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI; nSubmenuCtrl++; } } + +/* Add hyperlinks depending on the existence and values of special +** parameters in the request's query string. The names of a query's +** parameters that are investigated are obtainted by concatenation of +** the caller-provided zPrefix with suffix "smplXY", where optional +** ending XY consists of a digit X from the set {1,2,3,4,5} and an +** optional letter Y which (if present) must be either 'a' or 's'. +** zPrefix must start with a lowercase letter, +** be short and have no strange characters. Parameter's value +** is well-formed if its first filepath segment (separated by '/') +** has no strange characters. Malformed values are silently ignored. +** +** The text for the resulting submenu label equals to the value of the +** parameter modulus some prettification for better UX: +** 1) If a parameter's value starts with a lowercase letter and +** contains '/' then it goes unchanged into the user-visible label. +** 2a) If the first letter is uppercase then the label is +** truncated at the first '/' (if any), +** 2b) otherwise the first letter is capitalized. +** 3) Underscores in the first path segment are replaced with spaces. +** 4) If the resulting label starts with an uppercase letter +** then it is prepended with "✧" symbol for explicit distinction +** from the built-in labels +** +** Important security-related note: +** zLabel and zLink are formatted using %s because it is expected that +** style_finish_page() provides propper escaping via %h format. +*/ +void style_submenu_parametric( + const char *zPrefix /* common prefix of the query parameters names */ +){ + static const char *suffix = "smpl"; /* common suffix for param names */ + static const short sfxlen = 4; /* length of the above suffix */ + static const char sfxext[3] = {'a','s',0}; /* extra suffix ending */ + const char *zQS; /* QUERY_STRING */ + char zN[32]; /* buffer for parameter names to probe */ + short i,j,l; + + /* zPrefix must be tidy and short; also filter out ENV/CGI variables */ + assert( zPrefix != 0 && fossil_islower(zPrefix[0]) ); + l = strnlen( zPrefix, sizeof(zN) ); + assert( l+sfxlen+3 <= sizeof(zN) ); + assert( fossil_no_strange_characters(zPrefix) ); + /* concatenate zPrefix and suffix */ + strcpy( zN, zPrefix ); + strcpy( zN + l, suffix ); + l += sfxlen; + zN[l+2] = 0; /* nul-terminator after ...smplXY suffix */ + zQS = PD("QUERY_STRING",""); + for( i = 0; i <= 5; i++ ){ + zN[l] = ( i == 0 ? 0 : '0' + i ); /* ...smpl instead of ...smpl0 */ + for( j = (i ? 0 : sizeof(sfxext)-1); j < sizeof(sfxext); j++ ){ + const char *zV, *z; + zN[l+1] = sfxext[j]; + zV = PD(zN,""); + if( zV[0] == 0 || zV[0] == '/' || zV[0] == '_' || zV[0] == '-' ){ + continue; + } + /* require the first path segment to be unfancy ASCII string */ + for( z = zV; z[0] && z[0] != '/' ;){ + if( fossil_isalnum(z[0]) || z[0]=='_' || z[0]=='-' ) z++; + else break; + } + if( z[0] == '/' ){ + /* values may not contain "../" or "<" */ + if( strstr(z,"../")!=NULL || strstr(z,"<")!=NULL ){ + continue; + } + } + else if( z[0] != 0 ) + continue; + + assert( nSubmenu < count(aSubmenu) ); + if(fossil_islower(zV[0]) && z[0]=='/'){ + aSubmenu[nSubmenu].zLabel = mprintf( "%s",zV); /* memory leak? */ + }else{ + /* prepend a label with an unobtrusive symbol that "sorts-last"; + ** this clearly distincts it from the built-in elements */ + static const char *mark = "✧"; + char *z = mprintf("%s%s",mark,zV); + aSubmenu[nSubmenu].zLabel = z; + /* also prettify the first segment */ + z += strlen(mark); + z[0] = fossil_toupper(z[0]); + for(; z[0]!=0; z++ ){ + if( z[0]=='_' ) z[0] = ' '; + else if( z[0] == '/' ){ /* show just the first segment */ + z[0] = 0; + break; + } + } + } + if( zQS[0] ){ + aSubmenu[nSubmenu].zLink = mprintf("%R/%s?%s",zV,zQS); + }else{ + aSubmenu[nSubmenu].zLink = mprintf("%R/%s",zV); + } + nSubmenu++; + } + } +} /* ** Disable or enable the submenu */ void style_submenu_enable(int onOff){ @@ -944,11 +1046,11 @@ qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare); for(i=0; izLink==0 ){ @ %h(p->zLabel) }else{ @ %h(p->zLabel) Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -593,10 +593,11 @@ }else if( rid && g.perm.ApndWiki ){ style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName); } if( g.perm.Hyperlink ){ style_submenu_element("History", "%R/whistory?name=%T", zPageName); + style_submenu_parametric("wiki"); } } if( !isPopup ){ style_set_current_page("%T?name=%T", g.zPath, zPageName); wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -29,10 +29,13 @@ command. * The [/help?cmd=/ext|/ext page] generates the SERVER_SOFTWARE environment variable for clients. * The [/help?cmd=tar|tar] and [/help?cmd=zip|zip commands] no longer sterilize the manifest file. + * Submenu of the [/help?cmd=/rptview|/rptview] and + [/help?cmd=/wiki|/wiki] pages may be + [branch/rptview-submenu-paralink|extended with auxiliary hyperlinks].

Changes for version 2.17 (2021-10-09)

* Major improvements to the "diff" subsystem, including: