Fossil SCM

Initialize the $title and $login variable for TH1 used during header/footer generation to a similar string that has characters that are special to HTML escaped to similar-looking unicode characters. This is an defense against XSS attacks that might otherwise result due to errors in a custom skin where the $title variable is misused.

drh 2025-03-29 16:00 trunk
Commit 5ea6e15bf1fc942d67d53d085b4a67fedaafc13f45a6d1028a8bd637e9d513a3
2 files changed +72 +2 -2
+72
--- src/encode.c
+++ src/encode.c
@@ -141,10 +141,82 @@
141141
break;
142142
}
143143
}
144144
if( j<i ) blob_append(p, zIn+j, i-j);
145145
}
146
+
147
+/*
148
+** Make the given string safe for HTML by converting syntax characters
149
+** into alternatives that do not have the special syntactic meaning.
150
+**
151
+** < --> U+227a
152
+** > --> U+227b
153
+** & --> +
154
+** " --> U+201d
155
+** ' --> U+2019
156
+**
157
+** Return a pointer to a new string obtained from fossil_malloc().
158
+*/
159
+char *html_lookalike(const char *z, int n){
160
+ unsigned char c;
161
+ int i = 0;
162
+ int count = 0;
163
+ unsigned char *zOut;
164
+ const unsigned char *zIn = (const unsigned char*)z;
165
+
166
+ if( n<0 ) n = strlen(z);
167
+ while( i<n ){
168
+ switch( zIn[i] ){
169
+ case '<': count += 3; break;
170
+ case '>': count += 3; break;
171
+ case '"': count += 3; break;
172
+ case '\'': count += 3; break;
173
+ case 0: n = i; break;
174
+ }
175
+ i++;
176
+ }
177
+ i = 0;
178
+ zOut = fossil_malloc( count+n+1 );
179
+ if( count==0 ){
180
+ memcpy(zOut, zIn, n);
181
+ zOut[n] = 0;
182
+ return (char*)zOut;
183
+ }
184
+ while( n-->0 ){
185
+ c = *(zIn++);
186
+ switch( c ){
187
+ case '<':
188
+ zOut[i++] = 0xe2;
189
+ zOut[i++] = 0x89;
190
+ zOut[i++] = 0xba;
191
+ break;
192
+ case '>':
193
+ zOut[i++] = 0xe2;
194
+ zOut[i++] = 0x89;
195
+ zOut[i++] = 0xbb;
196
+ break;
197
+ case '&':
198
+ zOut[i++] = '+';
199
+ break;
200
+ case '"':
201
+ zOut[i++] = 0xe2;
202
+ zOut[i++] = 0x80;
203
+ zOut[i++] = 0x9d;
204
+ break;
205
+ case '\'':
206
+ zOut[i++] = 0xe2;
207
+ zOut[i++] = 0x80;
208
+ zOut[i++] = 0x99;
209
+ break;
210
+ default:
211
+ zOut[i++] = c;
212
+ break;
213
+ }
214
+ }
215
+ zOut[i] = 0;
216
+ return (char*)zOut;
217
+}
146218
147219
148220
/*
149221
** Encode a string for HTTP. This means converting lots of
150222
** characters into the "%HH" where H is a hex digit. It also
151223
--- src/encode.c
+++ src/encode.c
@@ -141,10 +141,82 @@
141 break;
142 }
143 }
144 if( j<i ) blob_append(p, zIn+j, i-j);
145 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
147
148 /*
149 ** Encode a string for HTTP. This means converting lots of
150 ** characters into the "%HH" where H is a hex digit. It also
151
--- src/encode.c
+++ src/encode.c
@@ -141,10 +141,82 @@
141 break;
142 }
143 }
144 if( j<i ) blob_append(p, zIn+j, i-j);
145 }
146
147 /*
148 ** Make the given string safe for HTML by converting syntax characters
149 ** into alternatives that do not have the special syntactic meaning.
150 **
151 ** < --> U+227a
152 ** > --> U+227b
153 ** & --> +
154 ** " --> U+201d
155 ** ' --> U+2019
156 **
157 ** Return a pointer to a new string obtained from fossil_malloc().
158 */
159 char *html_lookalike(const char *z, int n){
160 unsigned char c;
161 int i = 0;
162 int count = 0;
163 unsigned char *zOut;
164 const unsigned char *zIn = (const unsigned char*)z;
165
166 if( n<0 ) n = strlen(z);
167 while( i<n ){
168 switch( zIn[i] ){
169 case '<': count += 3; break;
170 case '>': count += 3; break;
171 case '"': count += 3; break;
172 case '\'': count += 3; break;
173 case 0: n = i; break;
174 }
175 i++;
176 }
177 i = 0;
178 zOut = fossil_malloc( count+n+1 );
179 if( count==0 ){
180 memcpy(zOut, zIn, n);
181 zOut[n] = 0;
182 return (char*)zOut;
183 }
184 while( n-->0 ){
185 c = *(zIn++);
186 switch( c ){
187 case '<':
188 zOut[i++] = 0xe2;
189 zOut[i++] = 0x89;
190 zOut[i++] = 0xba;
191 break;
192 case '>':
193 zOut[i++] = 0xe2;
194 zOut[i++] = 0x89;
195 zOut[i++] = 0xbb;
196 break;
197 case '&':
198 zOut[i++] = '+';
199 break;
200 case '"':
201 zOut[i++] = 0xe2;
202 zOut[i++] = 0x80;
203 zOut[i++] = 0x9d;
204 break;
205 case '\'':
206 zOut[i++] = 0xe2;
207 zOut[i++] = 0x80;
208 zOut[i++] = 0x99;
209 break;
210 default:
211 zOut[i++] = c;
212 break;
213 }
214 }
215 zOut[i] = 0;
216 return (char*)zOut;
217 }
218
219
220 /*
221 ** Encode a string for HTTP. This means converting lots of
222 ** characters into the "%HH" where H is a hex digit. It also
223
+2 -2
--- src/style.c
+++ src/style.c
@@ -746,11 +746,11 @@
746746
Th_MaybeStore("default_csp", zDfltCsp);
747747
fossil_free(zDfltCsp);
748748
Th_Store("nonce", zNonce);
749749
Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750750
Th_Store("project_description", db_get("project-description",""));
751
- if( zTitle ) Th_Store("title", zTitle);
751
+ if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752752
Th_Store("baseurl", g.zBaseURL);
753753
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754754
Th_Store("home", g.zTop);
755755
Th_Store("index_page", db_get("index-page","/home"));
756756
if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
@@ -772,11 +772,11 @@
772772
Th_Store("mainmenu", style_get_mainmenu());
773773
stylesheet_url_var();
774774
image_url_var("logo");
775775
image_url_var("background");
776776
if( !login_is_nobody() ){
777
- Th_Store("login", g.zLogin);
777
+ Th_Store("login", html_lookalike(g.zLogin,-1));
778778
}
779779
Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
780780
if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
781781
g.ftntsIssues[2] || g.ftntsIssues[3] ){
782782
char buf[80];
783783
--- src/style.c
+++ src/style.c
@@ -746,11 +746,11 @@
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750 Th_Store("project_description", db_get("project-description",""));
751 if( zTitle ) Th_Store("title", zTitle);
752 Th_Store("baseurl", g.zBaseURL);
753 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754 Th_Store("home", g.zTop);
755 Th_Store("index_page", db_get("index-page","/home"));
756 if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
@@ -772,11 +772,11 @@
772 Th_Store("mainmenu", style_get_mainmenu());
773 stylesheet_url_var();
774 image_url_var("logo");
775 image_url_var("background");
776 if( !login_is_nobody() ){
777 Th_Store("login", g.zLogin);
778 }
779 Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
780 if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
781 g.ftntsIssues[2] || g.ftntsIssues[3] ){
782 char buf[80];
783
--- src/style.c
+++ src/style.c
@@ -746,11 +746,11 @@
746 Th_MaybeStore("default_csp", zDfltCsp);
747 fossil_free(zDfltCsp);
748 Th_Store("nonce", zNonce);
749 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
750 Th_Store("project_description", db_get("project-description",""));
751 if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
752 Th_Store("baseurl", g.zBaseURL);
753 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
754 Th_Store("home", g.zTop);
755 Th_Store("index_page", db_get("index-page","/home"));
756 if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
@@ -772,11 +772,11 @@
772 Th_Store("mainmenu", style_get_mainmenu());
773 stylesheet_url_var();
774 image_url_var("logo");
775 image_url_var("background");
776 if( !login_is_nobody() ){
777 Th_Store("login", html_lookalike(g.zLogin,-1));
778 }
779 Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
780 if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
781 g.ftntsIssues[2] || g.ftntsIssues[3] ){
782 char buf[80];
783

Keyboard Shortcuts

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