Fossil SCM

Use a Cookie, instead of a custom HTTP header and/or URL param, to send the sync login header, as suggested in [forum:9959d2d9d9be22d2 | forum post 9959d2d9d9be22d2]. This is simpler.

stephan 2025-07-24 05:10 xfer-login-card
Commit 756ad2f23c67a4c817fb39f6d623dc5937bb8a549771fb164807f866e304b214
4 files changed +10 -12 +3 -32 +7 -7 +8 -14
+10 -12
--- src/cgi.c
+++ src/cgi.c
@@ -1285,12 +1285,12 @@
12851285
** Checks the QUERY_STRING environment variable, sets it up via
12861286
** add_param_list() and, if found, applies its "skin" setting. Returns
12871287
** 0 if no QUERY_STRING is set, else it returns a bitmask of:
12881288
**
12891289
** 0x01 = QUERY_STRING was set up
1290
-** 0x02 = "skin" GET arg was processed
1291
-** 0x04 = "x-f-x-l" GET arg was processed.
1290
+** 0x02 = "skin" URL param arg was processed
1291
+** 0x04 = "x-f-x-l" cookie arg was processed.
12921292
**
12931293
* In the case of the skin, the cookie may still need flushing
12941294
** by the page, via cookie_render().
12951295
*/
12961296
int cgi_setup_query_string(void){
@@ -1311,20 +1311,18 @@
13111311
** environment here. */
13121312
cgi_set_parameter_nocopy("udc", "1", 1);
13131313
}
13141314
fossil_free(zErr);
13151315
}
1316
- if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){
1317
- /* CGI fossil instances do not read the HTTP headers, so
1318
- ** they cannot see the X-Fossil-Xfer-Login card. As a consolation
1319
- ** to them, we'll accept that via this query argument. */
1320
- rc |= 0x04;
1321
- fossil_free( g.syncInfo.zLoginCard );
1322
- g.syncInfo.zLoginCard = fossil_strdup(z);
1323
- g.syncInfo.fLoginCardMode |= 0x10;
1324
- cgi_delete_parameter("x-f-x-l");
1325
- }
1316
+ }
1317
+ if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){
1318
+ /* X-Fossil-Xfer-Login card transmitted via cookie instead of in
1319
+ ** the sync payload. */
1320
+ rc |= 0x04;
1321
+ g.syncInfo.zLoginCard = fossil_strdup(z);
1322
+ g.syncInfo.fLoginCardMode |= 0x04;
1323
+ cgi_delete_parameter("x-f-x-l");
13261324
}
13271325
return rc;
13281326
}
13291327
13301328
/*
13311329
--- src/cgi.c
+++ src/cgi.c
@@ -1285,12 +1285,12 @@
1285 ** Checks the QUERY_STRING environment variable, sets it up via
1286 ** add_param_list() and, if found, applies its "skin" setting. Returns
1287 ** 0 if no QUERY_STRING is set, else it returns a bitmask of:
1288 **
1289 ** 0x01 = QUERY_STRING was set up
1290 ** 0x02 = "skin" GET arg was processed
1291 ** 0x04 = "x-f-x-l" GET arg was processed.
1292 **
1293 * In the case of the skin, the cookie may still need flushing
1294 ** by the page, via cookie_render().
1295 */
1296 int cgi_setup_query_string(void){
@@ -1311,20 +1311,18 @@
1311 ** environment here. */
1312 cgi_set_parameter_nocopy("udc", "1", 1);
1313 }
1314 fossil_free(zErr);
1315 }
1316 if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){
1317 /* CGI fossil instances do not read the HTTP headers, so
1318 ** they cannot see the X-Fossil-Xfer-Login card. As a consolation
1319 ** to them, we'll accept that via this query argument. */
1320 rc |= 0x04;
1321 fossil_free( g.syncInfo.zLoginCard );
1322 g.syncInfo.zLoginCard = fossil_strdup(z);
1323 g.syncInfo.fLoginCardMode |= 0x10;
1324 cgi_delete_parameter("x-f-x-l");
1325 }
1326 }
1327 return rc;
1328 }
1329
1330 /*
1331
--- src/cgi.c
+++ src/cgi.c
@@ -1285,12 +1285,12 @@
1285 ** Checks the QUERY_STRING environment variable, sets it up via
1286 ** add_param_list() and, if found, applies its "skin" setting. Returns
1287 ** 0 if no QUERY_STRING is set, else it returns a bitmask of:
1288 **
1289 ** 0x01 = QUERY_STRING was set up
1290 ** 0x02 = "skin" URL param arg was processed
1291 ** 0x04 = "x-f-x-l" cookie arg was processed.
1292 **
1293 * In the case of the skin, the cookie may still need flushing
1294 ** by the page, via cookie_render().
1295 */
1296 int cgi_setup_query_string(void){
@@ -1311,20 +1311,18 @@
1311 ** environment here. */
1312 cgi_set_parameter_nocopy("udc", "1", 1);
1313 }
1314 fossil_free(zErr);
1315 }
1316 }
1317 if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){
1318 /* X-Fossil-Xfer-Login card transmitted via cookie instead of in
1319 ** the sync payload. */
1320 rc |= 0x04;
1321 g.syncInfo.zLoginCard = fossil_strdup(z);
1322 g.syncInfo.fLoginCardMode |= 0x04;
1323 cgi_delete_parameter("x-f-x-l");
 
 
1324 }
1325 return rc;
1326 }
1327
1328 /*
1329
+3 -32
--- src/http.c
+++ src/http.c
@@ -128,33 +128,10 @@
128128
blob_reset(&pw);
129129
blob_reset(&sig);
130130
blob_reset(&nonce);
131131
}
132132
133
-/*
134
-** If we're in "login card header" mode, append ?x-f-x-l=ABC to
135
-** g.url.path, replacing any "?..." part of g.url.path. ABC = the
136
-** %T-encoded contents of pLogin. This is workaround for feeding the
137
-** login card to CGI-hosted fossil instances, as those do not read the
138
-** HTTP headers so cannot see the X-Fossil-Xfer-Login (x-f-x-l)
139
-** header.
140
-*/
141
-static void url_append_login_card(Blob * const pLogin){
142
- if( g.syncInfo.fLoginCardMode ||
143
- g.syncInfo.remoteVersion >= RELEASE_VERSION_NUMBER ){
144
- char * x;
145
- char * z = g.url.path;
146
- while( z && *z && '?'!=*z ) ++z;
147
- if( z && *z ) *z = 0;
148
- x = mprintf("%s?x-f-x-l=%T", g.url.path ? g.url.path : "/",
149
- blob_str(pLogin));
150
- fossil_free(g.url.path);
151
- g.url.path = x;
152
- g.syncInfo.fLoginCardMode |= 0x04;
153
- }
154
-}
155
-
156133
/*
157134
** Construct an appropriate HTTP request header. Write the header
158135
** into pHdr. This routine initializes the pHdr blob. pPayload is
159136
** the complete payload (including the login card if pLogin is NULL or
160137
** empty) already compressed.
@@ -166,14 +143,10 @@
166143
const char *zAltMimetype /* Alternative mimetype */
167144
){
168145
int nPayload = pPayload ? blob_size(pPayload) : 0;
169146
170147
blob_zero(pHdr);
171
- if( nPayload>0 && pLogin && blob_size(pLogin)>0 ){
172
- /* Add login card URL arg for POST requests */
173
- url_append_login_card(pLogin);
174
- }
175148
blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
176149
nPayload>0 ? "POST" : "GET",
177150
(g.url.path && g.url.path[0]) ? g.url.path : "/");
178151
if( g.url.proxyAuth ){
179152
blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
@@ -185,15 +158,13 @@
185158
fossil_free(zEncoded);
186159
}
187160
blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
188161
blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
189162
if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
190
- if( pLogin && blob_size(pLogin) ){
191
- blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin)
192
- /* Noting that CGIs can't read headers, but test-http can. If we
193
- ** set this _only_ as a URL argument then we lose that info for
194
- ** purposes of feeding it back through test-http. */;
163
+ if( nPayload>0 && pLogin && blob_size(pLogin) ){
164
+ /* Add login card via a transient cookie. */
165
+ blob_appendf(pHdr, "Cookie: x-f-x-l=%T\r\n", blob_str(pLogin));
195166
}
196167
if( nPayload ){
197168
if( zAltMimetype ){
198169
blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
199170
}else if( g.fHttpTrace ){
200171
--- src/http.c
+++ src/http.c
@@ -128,33 +128,10 @@
128 blob_reset(&pw);
129 blob_reset(&sig);
130 blob_reset(&nonce);
131 }
132
133 /*
134 ** If we're in "login card header" mode, append ?x-f-x-l=ABC to
135 ** g.url.path, replacing any "?..." part of g.url.path. ABC = the
136 ** %T-encoded contents of pLogin. This is workaround for feeding the
137 ** login card to CGI-hosted fossil instances, as those do not read the
138 ** HTTP headers so cannot see the X-Fossil-Xfer-Login (x-f-x-l)
139 ** header.
140 */
141 static void url_append_login_card(Blob * const pLogin){
142 if( g.syncInfo.fLoginCardMode ||
143 g.syncInfo.remoteVersion >= RELEASE_VERSION_NUMBER ){
144 char * x;
145 char * z = g.url.path;
146 while( z && *z && '?'!=*z ) ++z;
147 if( z && *z ) *z = 0;
148 x = mprintf("%s?x-f-x-l=%T", g.url.path ? g.url.path : "/",
149 blob_str(pLogin));
150 fossil_free(g.url.path);
151 g.url.path = x;
152 g.syncInfo.fLoginCardMode |= 0x04;
153 }
154 }
155
156 /*
157 ** Construct an appropriate HTTP request header. Write the header
158 ** into pHdr. This routine initializes the pHdr blob. pPayload is
159 ** the complete payload (including the login card if pLogin is NULL or
160 ** empty) already compressed.
@@ -166,14 +143,10 @@
166 const char *zAltMimetype /* Alternative mimetype */
167 ){
168 int nPayload = pPayload ? blob_size(pPayload) : 0;
169
170 blob_zero(pHdr);
171 if( nPayload>0 && pLogin && blob_size(pLogin)>0 ){
172 /* Add login card URL arg for POST requests */
173 url_append_login_card(pLogin);
174 }
175 blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
176 nPayload>0 ? "POST" : "GET",
177 (g.url.path && g.url.path[0]) ? g.url.path : "/");
178 if( g.url.proxyAuth ){
179 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
@@ -185,15 +158,13 @@
185 fossil_free(zEncoded);
186 }
187 blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
188 blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
189 if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
190 if( pLogin && blob_size(pLogin) ){
191 blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin)
192 /* Noting that CGIs can't read headers, but test-http can. If we
193 ** set this _only_ as a URL argument then we lose that info for
194 ** purposes of feeding it back through test-http. */;
195 }
196 if( nPayload ){
197 if( zAltMimetype ){
198 blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
199 }else if( g.fHttpTrace ){
200
--- src/http.c
+++ src/http.c
@@ -128,33 +128,10 @@
128 blob_reset(&pw);
129 blob_reset(&sig);
130 blob_reset(&nonce);
131 }
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133 /*
134 ** Construct an appropriate HTTP request header. Write the header
135 ** into pHdr. This routine initializes the pHdr blob. pPayload is
136 ** the complete payload (including the login card if pLogin is NULL or
137 ** empty) already compressed.
@@ -166,14 +143,10 @@
143 const char *zAltMimetype /* Alternative mimetype */
144 ){
145 int nPayload = pPayload ? blob_size(pPayload) : 0;
146
147 blob_zero(pHdr);
 
 
 
 
148 blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
149 nPayload>0 ? "POST" : "GET",
150 (g.url.path && g.url.path[0]) ? g.url.path : "/");
151 if( g.url.proxyAuth ){
152 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
@@ -185,15 +158,13 @@
158 fossil_free(zEncoded);
159 }
160 blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
161 blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
162 if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
163 if( nPayload>0 && pLogin && blob_size(pLogin) ){
164 /* Add login card via a transient cookie. */
165 blob_appendf(pHdr, "Cookie: x-f-x-l=%T\r\n", blob_str(pLogin));
 
 
166 }
167 if( nPayload ){
168 if( zAltMimetype ){
169 blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
170 }else if( g.fHttpTrace ){
171
+7 -7
--- src/main.c
+++ src/main.c
@@ -295,20 +295,20 @@
295295
** header parser (cgi.c), xfer.c, and http.c. */
296296
struct {
297297
char *zLoginCard; /* Inbound "X-Fossil-Xfer-Login" request
298298
** header or "x-f-x-l" URL parameter. */
299299
int fLoginCardMode; /* If non-0, emit login cards in outbound
300
- ** requests as a HTTP header or URL
301
- ** parameter instead of as part of the
302
- ** payload. Gets activated on-demand based
303
- ** on xfer traffic contents. Values, for
300
+ ** requests as a HTTP cookie instead of as
301
+ ** part of the payload. Gets activated
302
+ ** on-demand based on xfer traffic
303
+ ** contents. Values, for
304304
** diagnostic/debugging purposes: 0x01=CLI
305305
** --flag, 0x02=http_exchange(),
306
- ** 0x04=url_append_login_card(),
306
+ ** 0x04=cgi_setup_query_string(),
307307
** 0x08=cgi_handle_cgi_request(),
308
- ** 0x10=cgi_setup_query_string(),
309
- ** 0x20=page_xfer(), 0x40=client_sync(). */
308
+ ** 0x20=page_xfer(),
309
+ ** 0x40=client_sync(). */
310310
int remoteVersion; /* Remote fossil version. Used for negotiating
311311
** how to handle the login card. */
312312
} syncInfo;
313313
#ifdef FOSSIL_ENABLE_JSON
314314
struct FossilJsonBits {
315315
--- src/main.c
+++ src/main.c
@@ -295,20 +295,20 @@
295 ** header parser (cgi.c), xfer.c, and http.c. */
296 struct {
297 char *zLoginCard; /* Inbound "X-Fossil-Xfer-Login" request
298 ** header or "x-f-x-l" URL parameter. */
299 int fLoginCardMode; /* If non-0, emit login cards in outbound
300 ** requests as a HTTP header or URL
301 ** parameter instead of as part of the
302 ** payload. Gets activated on-demand based
303 ** on xfer traffic contents. Values, for
304 ** diagnostic/debugging purposes: 0x01=CLI
305 ** --flag, 0x02=http_exchange(),
306 ** 0x04=url_append_login_card(),
307 ** 0x08=cgi_handle_cgi_request(),
308 ** 0x10=cgi_setup_query_string(),
309 ** 0x20=page_xfer(), 0x40=client_sync(). */
310 int remoteVersion; /* Remote fossil version. Used for negotiating
311 ** how to handle the login card. */
312 } syncInfo;
313 #ifdef FOSSIL_ENABLE_JSON
314 struct FossilJsonBits {
315
--- src/main.c
+++ src/main.c
@@ -295,20 +295,20 @@
295 ** header parser (cgi.c), xfer.c, and http.c. */
296 struct {
297 char *zLoginCard; /* Inbound "X-Fossil-Xfer-Login" request
298 ** header or "x-f-x-l" URL parameter. */
299 int fLoginCardMode; /* If non-0, emit login cards in outbound
300 ** requests as a HTTP cookie instead of as
301 ** part of the payload. Gets activated
302 ** on-demand based on xfer traffic
303 ** contents. Values, for
304 ** diagnostic/debugging purposes: 0x01=CLI
305 ** --flag, 0x02=http_exchange(),
306 ** 0x04=cgi_setup_query_string(),
307 ** 0x08=cgi_handle_cgi_request(),
308 ** 0x20=page_xfer(),
309 ** 0x40=client_sync(). */
310 int remoteVersion; /* Remote fossil version. Used for negotiating
311 ** how to handle the login card. */
312 } syncInfo;
313 #ifdef FOSSIL_ENABLE_JSON
314 struct FossilJsonBits {
315
+8 -14
--- www/sync.wiki
+++ www/sync.wiki
@@ -239,24 +239,18 @@
239239
a sync error. (Prior to 2025-07-21, the protocol permitted multiple
240240
logins, treating the login as the union of all privileges from all
241241
login cards. That capability was never used and has been removed.)
242242
243243
As of version 2.27, Fossil supports transfering of the login card
244
-externally to the request payload in one of the following ways:
245
-
246
-<ul>
247
-<li> URL parameter named "x-f-x-l".
248
-<li> An HTTP header named "X-Fossil-Xfer-Login". The caveat for this
249
- header is that CGI-hosted fossils cannot see the headers. It
250
- works for standalone severs and connections running via fossil's
251
- "test-http" mechanism.
252
-</ul>
253
-
254
-It is legal to use both of those approaches together but it is not
255
-possible to use either of them with an in-body login card because
256
-including an in-body login card would change the login card's value
257
-for the header or URL parameter.
244
+externally to the request payload via a Cookie HTTP header:
245
+
246
+<verbatim>
247
+ Cookie: x-f-x-l=...
248
+</verbatim>
249
+
250
+Where "..." is the URL-encoded login cookie. <code>x-f-x-l</code> is
251
+short for X-Fossil-Xfer-Login.
258252
259253
260254
<h3 id="file">3.3 File Cards</h3>
261255
262256
Artifacts are transferred using either "file" cards, or "cfile"
263257
--- www/sync.wiki
+++ www/sync.wiki
@@ -239,24 +239,18 @@
239 a sync error. (Prior to 2025-07-21, the protocol permitted multiple
240 logins, treating the login as the union of all privileges from all
241 login cards. That capability was never used and has been removed.)
242
243 As of version 2.27, Fossil supports transfering of the login card
244 externally to the request payload in one of the following ways:
245
246 <ul>
247 <li> URL parameter named "x-f-x-l".
248 <li> An HTTP header named "X-Fossil-Xfer-Login". The caveat for this
249 header is that CGI-hosted fossils cannot see the headers. It
250 works for standalone severs and connections running via fossil's
251 "test-http" mechanism.
252 </ul>
253
254 It is legal to use both of those approaches together but it is not
255 possible to use either of them with an in-body login card because
256 including an in-body login card would change the login card's value
257 for the header or URL parameter.
258
259
260 <h3 id="file">3.3 File Cards</h3>
261
262 Artifacts are transferred using either "file" cards, or "cfile"
263
--- www/sync.wiki
+++ www/sync.wiki
@@ -239,24 +239,18 @@
239 a sync error. (Prior to 2025-07-21, the protocol permitted multiple
240 logins, treating the login as the union of all privileges from all
241 login cards. That capability was never used and has been removed.)
242
243 As of version 2.27, Fossil supports transfering of the login card
244 externally to the request payload via a Cookie HTTP header:
245
246 <verbatim>
247 Cookie: x-f-x-l=...
248 </verbatim>
249
250 Where "..." is the URL-encoded login cookie. <code>x-f-x-l</code> is
251 short for X-Fossil-Xfer-Login.
 
 
 
 
 
 
252
253
254 <h3 id="file">3.3 File Cards</h3>
255
256 Artifacts are transferred using either "file" cards, or "cfile"
257

Keyboard Shortcuts

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