Fossil SCM

The stricter attribute checking applied in [6436ecf911] cause the img srcset attribute to not get the repo-root (%R) rewriting that src gets. This breaks existing docs that used this for resolution-dependent image rendering in MD and such. For testing the fix, this adds the --top flag to the test-safe-html command, since a regular "fossil ui" doesn't show the problem; it either has to be hosted underneath a URL by an RP server or via this test option to see the effect of %R rewriting.

wyoung 2026-07-01 01:00 UTC trunk
Commit fbffbe115252be5e20782643eeb542186f7903332c28589fc5f02577b6760760
1 file changed +55 -7
+55 -7
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -984,10 +984,57 @@
984984
while( fossil_isspace(z[i]) ){ i++; }
985985
if( z[i]==0 || z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
986986
}
987987
return seen;
988988
}
989
+
990
+/*
991
+** Append an HTML attribute value, rewriting root-relative URLs in src
992
+** and srcset the same way (%R prefix).
993
+*/
994
+static void appendRewrittenAttrValue(Blob *pOut, int iACode, const char *zVal){
995
+ if( iACode==ATTR_SRC && zVal[0]=='/' ){
996
+ blob_appendf(pOut, "=\"%R%s\"", zVal);
997
+ return;
998
+ }
999
+ if( iACode==ATTR_SRCSET ){
1000
+ const char *z = zVal;
1001
+ int nCand = 0;
1002
+ blob_append_char(pOut, '=');
1003
+ blob_append_char(pOut, '"');
1004
+ while( z[0] ){
1005
+ const char *zComma = strchr(z, ',');
1006
+ int len = zComma ? (int)(zComma - z) : (int)strlen(z);
1007
+ const char *zCand = z;
1008
+ int urlStart = 0;
1009
+ int urlLen;
1010
+ if( len>0 ){
1011
+ while( urlStart<len && fossil_isspace(zCand[urlStart]) ) urlStart++;
1012
+ urlLen = urlStart;
1013
+ while( urlLen<len && !fossil_isspace(zCand[urlLen]) ) urlLen++;
1014
+ urlLen -= urlStart;
1015
+ if( nCand++ ) blob_append_char(pOut, ',');
1016
+ if( urlStart>0 ){
1017
+ blob_append(pOut, zCand, urlStart);
1018
+ }
1019
+ if( urlLen>0 && zCand[urlStart]=='/' ){
1020
+ blob_appendf(pOut, "%R%.*s", urlLen, zCand+urlStart);
1021
+ }else if( urlLen>0 ){
1022
+ blob_appendf(pOut, "%.*s", urlLen, zCand+urlStart);
1023
+ }
1024
+ if( urlStart+urlLen < len ){
1025
+ blob_appendf(pOut, "%.*s", len-urlStart-urlLen, zCand+urlStart+urlLen);
1026
+ }
1027
+ }
1028
+ if( zComma==0 ) break;
1029
+ z = zComma + 1;
1030
+ }
1031
+ blob_append_char(pOut, '"');
1032
+ return;
1033
+ }
1034
+ blob_appendf(pOut, "=\"%s\"", zVal);
1035
+}
9891036
9901037
/*
9911038
** Render markup on the given blob.
9921039
*/
9931040
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
@@ -997,16 +1044,11 @@
9971044
}else{
9981045
blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
9991046
for(i=0; i<p->nAttr; i++){
10001047
blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
10011048
if( p->aAttr[i].zValue ){
1002
- const char *zVal = p->aAttr[i].zValue;
1003
- if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){
1004
- blob_appendf(pOut, "=\"%R%s\"", zVal);
1005
- }else{
1006
- blob_appendf(pOut, "=\"%s\"", zVal);
1007
- }
1049
+ appendRewrittenAttrValue(pOut, p->aAttr[i].iACode, p->aAttr[i].zValue);
10081050
}
10091051
}
10101052
if (p->iType & MUTYPE_SINGLE){
10111053
blob_append_string(pOut, " /");
10121054
}
@@ -3105,19 +3147,25 @@
31053147
}
31063148
31073149
/*
31083150
** COMMAND: test-safe-html
31093151
**
3110
-** Usage: %fossil test-safe-html FILE ...
3152
+** Usage: %fossil test-safe-html ?OPTIONS? FILE ...
31113153
**
31123154
** Read files named on the command-line. Send the text of each file
31133155
** through safe_html_append() and then write the result on
31143156
** standard output.
3157
+**
3158
+** Options:
3159
+** --top PATH Use PATH as g.zTop when rewriting root-relative URLs
31153160
*/
31163161
void test_safe_html_cmd(void){
31173162
int i;
31183163
Blob x;
3164
+ const char *zTop = find_option("top",0,1);
3165
+ verify_all_options();
3166
+ if( zTop ) g.zTop = (char*)zTop;
31193167
for(i=2; i<g.argc; i++){
31203168
char *z;
31213169
int n;
31223170
blob_read_from_file(&x, g.argv[i], ExtFILE);
31233171
blob_terminate(&x);
31243172
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -984,10 +984,57 @@
984 while( fossil_isspace(z[i]) ){ i++; }
985 if( z[i]==0 || z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
986 }
987 return seen;
988 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
989
990 /*
991 ** Render markup on the given blob.
992 */
993 static void renderMarkup(Blob *pOut, ParsedMarkup *p){
@@ -997,16 +1044,11 @@
997 }else{
998 blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
999 for(i=0; i<p->nAttr; i++){
1000 blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
1001 if( p->aAttr[i].zValue ){
1002 const char *zVal = p->aAttr[i].zValue;
1003 if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){
1004 blob_appendf(pOut, "=\"%R%s\"", zVal);
1005 }else{
1006 blob_appendf(pOut, "=\"%s\"", zVal);
1007 }
1008 }
1009 }
1010 if (p->iType & MUTYPE_SINGLE){
1011 blob_append_string(pOut, " /");
1012 }
@@ -3105,19 +3147,25 @@
3105 }
3106
3107 /*
3108 ** COMMAND: test-safe-html
3109 **
3110 ** Usage: %fossil test-safe-html FILE ...
3111 **
3112 ** Read files named on the command-line. Send the text of each file
3113 ** through safe_html_append() and then write the result on
3114 ** standard output.
 
 
 
3115 */
3116 void test_safe_html_cmd(void){
3117 int i;
3118 Blob x;
 
 
 
3119 for(i=2; i<g.argc; i++){
3120 char *z;
3121 int n;
3122 blob_read_from_file(&x, g.argv[i], ExtFILE);
3123 blob_terminate(&x);
3124
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -984,10 +984,57 @@
984 while( fossil_isspace(z[i]) ){ i++; }
985 if( z[i]==0 || z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
986 }
987 return seen;
988 }
989
990 /*
991 ** Append an HTML attribute value, rewriting root-relative URLs in src
992 ** and srcset the same way (%R prefix).
993 */
994 static void appendRewrittenAttrValue(Blob *pOut, int iACode, const char *zVal){
995 if( iACode==ATTR_SRC && zVal[0]=='/' ){
996 blob_appendf(pOut, "=\"%R%s\"", zVal);
997 return;
998 }
999 if( iACode==ATTR_SRCSET ){
1000 const char *z = zVal;
1001 int nCand = 0;
1002 blob_append_char(pOut, '=');
1003 blob_append_char(pOut, '"');
1004 while( z[0] ){
1005 const char *zComma = strchr(z, ',');
1006 int len = zComma ? (int)(zComma - z) : (int)strlen(z);
1007 const char *zCand = z;
1008 int urlStart = 0;
1009 int urlLen;
1010 if( len>0 ){
1011 while( urlStart<len && fossil_isspace(zCand[urlStart]) ) urlStart++;
1012 urlLen = urlStart;
1013 while( urlLen<len && !fossil_isspace(zCand[urlLen]) ) urlLen++;
1014 urlLen -= urlStart;
1015 if( nCand++ ) blob_append_char(pOut, ',');
1016 if( urlStart>0 ){
1017 blob_append(pOut, zCand, urlStart);
1018 }
1019 if( urlLen>0 && zCand[urlStart]=='/' ){
1020 blob_appendf(pOut, "%R%.*s", urlLen, zCand+urlStart);
1021 }else if( urlLen>0 ){
1022 blob_appendf(pOut, "%.*s", urlLen, zCand+urlStart);
1023 }
1024 if( urlStart+urlLen < len ){
1025 blob_appendf(pOut, "%.*s", len-urlStart-urlLen, zCand+urlStart+urlLen);
1026 }
1027 }
1028 if( zComma==0 ) break;
1029 z = zComma + 1;
1030 }
1031 blob_append_char(pOut, '"');
1032 return;
1033 }
1034 blob_appendf(pOut, "=\"%s\"", zVal);
1035 }
1036
1037 /*
1038 ** Render markup on the given blob.
1039 */
1040 static void renderMarkup(Blob *pOut, ParsedMarkup *p){
@@ -997,16 +1044,11 @@
1044 }else{
1045 blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
1046 for(i=0; i<p->nAttr; i++){
1047 blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName);
1048 if( p->aAttr[i].zValue ){
1049 appendRewrittenAttrValue(pOut, p->aAttr[i].iACode, p->aAttr[i].zValue);
 
 
 
 
 
1050 }
1051 }
1052 if (p->iType & MUTYPE_SINGLE){
1053 blob_append_string(pOut, " /");
1054 }
@@ -3105,19 +3147,25 @@
3147 }
3148
3149 /*
3150 ** COMMAND: test-safe-html
3151 **
3152 ** Usage: %fossil test-safe-html ?OPTIONS? FILE ...
3153 **
3154 ** Read files named on the command-line. Send the text of each file
3155 ** through safe_html_append() and then write the result on
3156 ** standard output.
3157 **
3158 ** Options:
3159 ** --top PATH Use PATH as g.zTop when rewriting root-relative URLs
3160 */
3161 void test_safe_html_cmd(void){
3162 int i;
3163 Blob x;
3164 const char *zTop = find_option("top",0,1);
3165 verify_all_options();
3166 if( zTop ) g.zTop = (char*)zTop;
3167 for(i=2; i<g.argc; i++){
3168 char *z;
3169 int n;
3170 blob_read_from_file(&x, g.argv[i], ExtFILE);
3171 blob_terminate(&x);
3172

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button