Fossil SCM

Add the internal-use /ajax/artifact.json?uuid=X route to support the forum editor. It emits artifact_to_json(X). Fix ajax_route_bootstrap()'s csrf check to trigger only for requests which require POST.

stephan 2026-06-07 16:47 UTC forum-editor-2026
Commit 35448ae96ade54917df7a3a0e15ace3513ef7401703225a88b17b54e126338a3
1 file changed +33 -3
+33 -3
--- src/ajax.c
+++ src/ajax.c
@@ -253,17 +253,18 @@
253253
int ajax_route_bootstrap(int requireWrite, int requirePost){
254254
login_check_credentials();
255255
if( requireWrite!=0 && g.perm.Write==0 ){
256256
ajax_route_error(403,"Write permissions required.");
257257
return 0;
258
- }else if(0==cgi_csrf_safe(requirePost)){
258
+ }else if(requirePost && 0==cgi_csrf_safe(requirePost)){
259259
ajax_route_error(403,
260260
"CSRF violation (make sure sending of HTTP "
261261
"Referer headers is enabled for XHR "
262262
"connections).");
263263
return 0;
264264
}
265
+ cgi_set_content_type("application/json");
265266
return 1;
266267
}
267268
268269
/*
269270
** Helper for collecting filename/check-in request parameters.
@@ -370,10 +371,37 @@
370371
}
371372
if(zRenderMode!=0){
372373
cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode);
373374
}
374375
}
376
+
377
+/*
378
+** AJAX route /ajax/artifact.json.
379
+** URL arguments:
380
+**
381
+** uuid=ARTIFACT_ID REQUIRED
382
+**
383
+** and emits either:
384
+**
385
+** { error: "..." }
386
+**
387
+** with a non-200 response code or the artifact's manifest in JSON
388
+** form with a 200 response code.
389
+*/
390
+void ajax_route_artifact_json(void){
391
+ const char *zUuid = P("uuid");
392
+ Blob json = BLOB_INITIALIZER;
393
+ login_check_credentials();
394
+ if( ! g.perm.Read ){
395
+ ajax_route_error_forbidden();
396
+ }else if( artifact_to_json_by_name(zUuid, &json) ){
397
+ @ %b(&json)
398
+ }else{
399
+ ajax_route_error_404("Cannot resolve artifact ID.");
400
+ }
401
+ blob_reset(&json);
402
+}
375403
376404
#if INTERFACE
377405
/*
378406
** Internal mapping of ajax sub-route names to various metadata.
379407
*/
@@ -380,11 +408,12 @@
380408
struct AjaxRoute {
381409
const char *zName; /* Name part of the route after "ajax/" */
382410
void (*xCallback)(); /* Impl function for the route. */
383411
int bWriteMode; /* True if requires write mode */
384412
int bPost; /* True if requires POST (i.e. CSRF
385
- ** verification) */
413
+ ** verification). Value is passed to
414
+ ** cgi_csrf_safe(). */
386415
};
387416
typedef struct AjaxRoute AjaxRoute;
388417
#endif /*INTERFACE*/
389418
390419
/*
@@ -430,11 +459,12 @@
430459
**
431460
** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898
432461
**
433462
** This particular route is used by /fileedit and /chat, whereas
434463
** /wikiedit uses a simpler wiki-specific route.
435
- */ }
464
+ */ },
465
+ {"artifact.json", ajax_route_artifact_json, 0, 0}
436466
};
437467
438468
if(zName==0 || zName[0]==0){
439469
ajax_route_error(400,"Missing required [route] 'name' parameter.");
440470
return;
441471
--- src/ajax.c
+++ src/ajax.c
@@ -253,17 +253,18 @@
253 int ajax_route_bootstrap(int requireWrite, int requirePost){
254 login_check_credentials();
255 if( requireWrite!=0 && g.perm.Write==0 ){
256 ajax_route_error(403,"Write permissions required.");
257 return 0;
258 }else if(0==cgi_csrf_safe(requirePost)){
259 ajax_route_error(403,
260 "CSRF violation (make sure sending of HTTP "
261 "Referer headers is enabled for XHR "
262 "connections).");
263 return 0;
264 }
 
265 return 1;
266 }
267
268 /*
269 ** Helper for collecting filename/check-in request parameters.
@@ -370,10 +371,37 @@
370 }
371 if(zRenderMode!=0){
372 cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode);
373 }
374 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
376 #if INTERFACE
377 /*
378 ** Internal mapping of ajax sub-route names to various metadata.
379 */
@@ -380,11 +408,12 @@
380 struct AjaxRoute {
381 const char *zName; /* Name part of the route after "ajax/" */
382 void (*xCallback)(); /* Impl function for the route. */
383 int bWriteMode; /* True if requires write mode */
384 int bPost; /* True if requires POST (i.e. CSRF
385 ** verification) */
 
386 };
387 typedef struct AjaxRoute AjaxRoute;
388 #endif /*INTERFACE*/
389
390 /*
@@ -430,11 +459,12 @@
430 **
431 ** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898
432 **
433 ** This particular route is used by /fileedit and /chat, whereas
434 ** /wikiedit uses a simpler wiki-specific route.
435 */ }
 
436 };
437
438 if(zName==0 || zName[0]==0){
439 ajax_route_error(400,"Missing required [route] 'name' parameter.");
440 return;
441
--- src/ajax.c
+++ src/ajax.c
@@ -253,17 +253,18 @@
253 int ajax_route_bootstrap(int requireWrite, int requirePost){
254 login_check_credentials();
255 if( requireWrite!=0 && g.perm.Write==0 ){
256 ajax_route_error(403,"Write permissions required.");
257 return 0;
258 }else if(requirePost && 0==cgi_csrf_safe(requirePost)){
259 ajax_route_error(403,
260 "CSRF violation (make sure sending of HTTP "
261 "Referer headers is enabled for XHR "
262 "connections).");
263 return 0;
264 }
265 cgi_set_content_type("application/json");
266 return 1;
267 }
268
269 /*
270 ** Helper for collecting filename/check-in request parameters.
@@ -370,10 +371,37 @@
371 }
372 if(zRenderMode!=0){
373 cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode);
374 }
375 }
376
377 /*
378 ** AJAX route /ajax/artifact.json.
379 ** URL arguments:
380 **
381 ** uuid=ARTIFACT_ID REQUIRED
382 **
383 ** and emits either:
384 **
385 ** { error: "..." }
386 **
387 ** with a non-200 response code or the artifact's manifest in JSON
388 ** form with a 200 response code.
389 */
390 void ajax_route_artifact_json(void){
391 const char *zUuid = P("uuid");
392 Blob json = BLOB_INITIALIZER;
393 login_check_credentials();
394 if( ! g.perm.Read ){
395 ajax_route_error_forbidden();
396 }else if( artifact_to_json_by_name(zUuid, &json) ){
397 @ %b(&json)
398 }else{
399 ajax_route_error_404("Cannot resolve artifact ID.");
400 }
401 blob_reset(&json);
402 }
403
404 #if INTERFACE
405 /*
406 ** Internal mapping of ajax sub-route names to various metadata.
407 */
@@ -380,11 +408,12 @@
408 struct AjaxRoute {
409 const char *zName; /* Name part of the route after "ajax/" */
410 void (*xCallback)(); /* Impl function for the route. */
411 int bWriteMode; /* True if requires write mode */
412 int bPost; /* True if requires POST (i.e. CSRF
413 ** verification). Value is passed to
414 ** cgi_csrf_safe(). */
415 };
416 typedef struct AjaxRoute AjaxRoute;
417 #endif /*INTERFACE*/
418
419 /*
@@ -430,11 +459,12 @@
459 **
460 ** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898
461 **
462 ** This particular route is used by /fileedit and /chat, whereas
463 ** /wikiedit uses a simpler wiki-specific route.
464 */ },
465 {"artifact.json", ajax_route_artifact_json, 0, 0}
466 };
467
468 if(zName==0 || zName[0]==0){
469 ajax_route_error(400,"Missing required [route] 'name' parameter.");
470 return;
471

Keyboard Shortcuts

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