Check-in [756ad2f23c]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Timelines: family | ancestors | descendants | both | xfer-login-card
Files: files | file ages | folders
SHA3-256: 756ad2f23c67a4c817fb39f6d623dc5937bb8a549771fb164807f866e304b214
User & Date: stephan 2025-07-24 05:10:06.726
Context
2025-07-24
05:26
Remove the now-obsolete parsing of the X-Fossil-Xfer-Login HTTP header. check-in: 8dbcf2acba user: stephan tags: xfer-login-card
05:10
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. check-in: 756ad2f23c user: stephan tags: xfer-login-card
03:16
Previous checkin should not have compiled - clean rebuild uncovered a stale dep. Re-map the fLoginCardMode to a bitmask so that it's possible to tell when multiple paths toggle that on, and which paths they were. check-in: 780d3b2fe3 user: stephan tags: xfer-login-card
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298

/*
** Checks the QUERY_STRING environment variable, sets it up via
** add_param_list() and, if found, applies its "skin" setting. Returns
** 0 if no QUERY_STRING is set, else it returns a bitmask of:
**
** 0x01 = QUERY_STRING was set up
** 0x02 = "skin" GET arg was processed
** 0x04 = "x-f-x-l" GET arg was processed.
**
*  In the case of the skin, the cookie may still need flushing
** by the page, via cookie_render().
*/
int cgi_setup_query_string(void){
  int rc = 0;
  char * z = (char*)P("QUERY_STRING");







|
|







1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298

/*
** Checks the QUERY_STRING environment variable, sets it up via
** add_param_list() and, if found, applies its "skin" setting. Returns
** 0 if no QUERY_STRING is set, else it returns a bitmask of:
**
** 0x01 = QUERY_STRING was set up
** 0x02 = "skin" URL param arg was processed
** 0x04 = "x-f-x-l" cookie arg was processed.
**
*  In the case of the skin, the cookie may still need flushing
** by the page, via cookie_render().
*/
int cgi_setup_query_string(void){
  int rc = 0;
  char * z = (char*)P("QUERY_STRING");
1309
1310
1311
1312
1313
1314
1315

1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
        /* Per /chat discussion, passing ?skin=... without "once"
        ** implies the "udc" argument, so we force that into the
        ** environment here. */
        cgi_set_parameter_nocopy("udc", "1", 1);
      }
      fossil_free(zErr);
    }

    if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){
      /* CGI fossil instances do not read the HTTP headers, so
      ** they cannot see the X-Fossil-Xfer-Login card. As a consolation
      ** to them, we'll accept that via this query argument. */
      rc |= 0x04;
      fossil_free( g.syncInfo.zLoginCard );
      g.syncInfo.zLoginCard = fossil_strdup(z);
      g.syncInfo.fLoginCardMode |= 0x10;
      cgi_delete_parameter("x-f-x-l");
    }
  }
  return rc;
}

/*
** Initialize the query parameter database.  Information is pulled from
** the QUERY_STRING environment variable (if it exists), from standard







>
|
<
|
|
|
<
|
|
|
<







1309
1310
1311
1312
1313
1314
1315
1316
1317

1318
1319
1320

1321
1322
1323

1324
1325
1326
1327
1328
1329
1330
        /* Per /chat discussion, passing ?skin=... without "once"
        ** implies the "udc" argument, so we force that into the
        ** environment here. */
        cgi_set_parameter_nocopy("udc", "1", 1);
      }
      fossil_free(zErr);
    }
  }
  if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){

    /* X-Fossil-Xfer-Login card transmitted via cookie instead of in
    ** the sync payload. */
    rc |= 0x04;

    g.syncInfo.zLoginCard = fossil_strdup(z);
    g.syncInfo.fLoginCardMode |= 0x04;
    cgi_delete_parameter("x-f-x-l");

  }
  return rc;
}

/*
** Initialize the query parameter database.  Information is pulled from
** the QUERY_STRING environment variable (if it exists), from standard
Changes to src/http.c.
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

191
192
193
194
195
196
197
198
199
200
201
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
  blob_reset(&pw);
  blob_reset(&sig);
  blob_reset(&nonce);
}

/*
** If we're in "login card header" mode, append ?x-f-x-l=ABC to
** g.url.path, replacing any "?..." part of g.url.path.  ABC = the
** %T-encoded contents of pLogin.  This is workaround for feeding the
** login card to CGI-hosted fossil instances, as those do not read the
** HTTP headers so cannot see the X-Fossil-Xfer-Login (x-f-x-l)
** header.
*/
static void url_append_login_card(Blob * const pLogin){
  if( g.syncInfo.fLoginCardMode ||
      g.syncInfo.remoteVersion >= RELEASE_VERSION_NUMBER ){
    char * x;
    char * z = g.url.path;
    while( z && *z && '?'!=*z ) ++z;
    if( z && *z ) *z = 0;
    x = mprintf("%s?x-f-x-l=%T", g.url.path ? g.url.path : "/",
                blob_str(pLogin));
    fossil_free(g.url.path);
    g.url.path = x;
    g.syncInfo.fLoginCardMode |= 0x04;
  }
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card if pLogin is NULL or
** empty) already compressed.
*/
static void http_build_header(
  Blob *pPayload,              /* the payload that will be sent */
  Blob *pHdr,                  /* construct the header here */
  Blob *pLogin,                /* Login card header value or NULL */
  const char *zAltMimetype     /* Alternative mimetype */
){
  int nPayload = pPayload ? blob_size(pPayload) : 0;

  blob_zero(pHdr);
  if( nPayload>0 && pLogin && blob_size(pLogin)>0 ){
    /* Add login card URL arg for POST requests */
    url_append_login_card(pLogin);
  }
  blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
               nPayload>0 ? "POST" : "GET",
               (g.url.path && g.url.path[0]) ? g.url.path : "/");
  if( g.url.proxyAuth ){
    blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
  }
  if( g.zHttpAuth && g.zHttpAuth[0] ){
    const char *zCredentials = g.zHttpAuth;
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( pLogin && blob_size(pLogin) ){

    blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin)
      /* Noting that CGIs can't read headers, but test-http can. If we
      ** set this _only_ as a URL argument then we lose that info for
      ** purposes of feeding it back through test-http. */;
  }
  if( nPayload ){
    if( zAltMimetype ){
      blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
    }else if( g.fHttpTrace ){
      blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
    }else{







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<















<
<
<
<















|
>
|
<
<
<







126
127
128
129
130
131
132























133
134
135
136
137
138
139
140
141
142
143
144
145
146
147




148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165



166
167
168
169
170
171
172
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
  blob_reset(&pw);
  blob_reset(&sig);
  blob_reset(&nonce);
}
























/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card if pLogin is NULL or
** empty) already compressed.
*/
static void http_build_header(
  Blob *pPayload,              /* the payload that will be sent */
  Blob *pHdr,                  /* construct the header here */
  Blob *pLogin,                /* Login card header value or NULL */
  const char *zAltMimetype     /* Alternative mimetype */
){
  int nPayload = pPayload ? blob_size(pPayload) : 0;

  blob_zero(pHdr);




  blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
               nPayload>0 ? "POST" : "GET",
               (g.url.path && g.url.path[0]) ? g.url.path : "/");
  if( g.url.proxyAuth ){
    blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
  }
  if( g.zHttpAuth && g.zHttpAuth[0] ){
    const char *zCredentials = g.zHttpAuth;
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( nPayload>0 && pLogin && blob_size(pLogin) ){
    /* Add login card via a transient cookie. */
    blob_appendf(pHdr, "Cookie: x-f-x-l=%T\r\n", blob_str(pLogin));



  }
  if( nPayload ){
    if( zAltMimetype ){
      blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
    }else if( g.fHttpTrace ){
      blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
    }else{
Changes to src/main.c.
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309

310
311
312
313
314
315
316

  /* State for communicating specific details between the inbound HTTP
  ** header parser (cgi.c), xfer.c, and http.c. */
  struct {
    char *zLoginCard;       /* Inbound "X-Fossil-Xfer-Login" request
                            ** header or "x-f-x-l" URL parameter. */
    int fLoginCardMode;     /* If non-0, emit login cards in outbound
                            ** requests as a HTTP header or URL
                            ** parameter instead of as part of the
                            ** payload. Gets activated on-demand based
                            ** on xfer traffic contents. Values, for
                            ** diagnostic/debugging purposes: 0x01=CLI
                            ** --flag, 0x02=http_exchange(),
                            ** 0x04=url_append_login_card(),
                            ** 0x08=cgi_handle_cgi_request(),
                            ** 0x10=cgi_setup_query_string(),
                            ** 0x20=page_xfer(), 0x40=client_sync(). */

    int remoteVersion;      /* Remote fossil version. Used for negotiating
                            ** how to handle the login card. */
  } syncInfo;
#ifdef FOSSIL_ENABLE_JSON
  struct FossilJsonBits {
    int isJsonMode;            /* True if running in JSON mode, else
                                  false. This changes how errors are







|
|
|
|


|

<
|
>







293
294
295
296
297
298
299
300
301
302
303
304
305
306
307

308
309
310
311
312
313
314
315
316

  /* State for communicating specific details between the inbound HTTP
  ** header parser (cgi.c), xfer.c, and http.c. */
  struct {
    char *zLoginCard;       /* Inbound "X-Fossil-Xfer-Login" request
                            ** header or "x-f-x-l" URL parameter. */
    int fLoginCardMode;     /* If non-0, emit login cards in outbound
                            ** requests as a HTTP cookie instead of as
                            ** part of the payload. Gets activated
                            ** on-demand based on xfer traffic
                            ** contents. Values, for
                            ** diagnostic/debugging purposes: 0x01=CLI
                            ** --flag, 0x02=http_exchange(),
                            ** 0x04=cgi_setup_query_string(),
                            ** 0x08=cgi_handle_cgi_request(),

                            ** 0x20=page_xfer(),
                            ** 0x40=client_sync(). */
    int remoteVersion;      /* Remote fossil version. Used for negotiating
                            ** how to handle the login card. */
  } syncInfo;
#ifdef FOSSIL_ENABLE_JSON
  struct FossilJsonBits {
    int isJsonMode;            /* True if running in JSON mode, else
                                  false. This changes how errors are
Changes to www/sync.wiki.
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

Only one login card is permitted. A second login card will trigger
a sync error. (Prior to 2025-07-21, the protocol permitted multiple
logins, treating the login as the union of all privileges from all
login cards. That capability was never used and has been removed.)

As of version 2.27, Fossil supports transfering of the login card
externally to the request payload in one of the following ways:

<ul>
<li> URL parameter named "x-f-x-l".
<li> An HTTP header named "X-Fossil-Xfer-Login". The caveat for this
     header is that CGI-hosted fossils cannot see the headers. It
     works for standalone severs and connections running via fossil's
     "test-http" mechanism.
</ul>

It is legal to use both of those approaches together but it is not
possible to use either of them with an in-body login card because
including an in-body login card would change the login card's value
for the header or URL parameter.


<h3 id="file">3.3 File Cards</h3>

Artifacts are transferred using either "file" cards, or "cfile"
or "uvfile" cards.
The name "file" card comes from the fact that most artifacts correspond to







|

|
|
<
<
<
<
|

<
|
<
|







237
238
239
240
241
242
243
244
245
246
247




248
249

250

251
252
253
254
255
256
257
258

Only one login card is permitted. A second login card will trigger
a sync error. (Prior to 2025-07-21, the protocol permitted multiple
logins, treating the login as the union of all privileges from all
login cards. That capability was never used and has been removed.)

As of version 2.27, Fossil supports transfering of the login card
externally to the request payload via a Cookie HTTP header:

<verbatim>
  Cookie: x-f-x-l=...




</verbatim>


Where "..." is the URL-encoded login cookie. <code>x-f-x-l</code> is

short for X-Fossil-Xfer-Login.


<h3 id="file">3.3 File Cards</h3>

Artifacts are transferred using either "file" cards, or "cfile"
or "uvfile" cards.
The name "file" card comes from the fact that most artifacts correspond to