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.
Commit
35448ae96ade54917df7a3a0e15ace3513ef7401703225a88b17b54e126338a3
Parent
44aafa6117f4c08…
1 file changed
+33
-3
+33
-3
| --- src/ajax.c | ||
| +++ src/ajax.c | ||
| @@ -253,17 +253,18 @@ | ||
| 253 | 253 | int ajax_route_bootstrap(int requireWrite, int requirePost){ |
| 254 | 254 | login_check_credentials(); |
| 255 | 255 | if( requireWrite!=0 && g.perm.Write==0 ){ |
| 256 | 256 | ajax_route_error(403,"Write permissions required."); |
| 257 | 257 | return 0; |
| 258 | - }else if(0==cgi_csrf_safe(requirePost)){ | |
| 258 | + }else if(requirePost && 0==cgi_csrf_safe(requirePost)){ | |
| 259 | 259 | ajax_route_error(403, |
| 260 | 260 | "CSRF violation (make sure sending of HTTP " |
| 261 | 261 | "Referer headers is enabled for XHR " |
| 262 | 262 | "connections)."); |
| 263 | 263 | return 0; |
| 264 | 264 | } |
| 265 | + cgi_set_content_type("application/json"); | |
| 265 | 266 | return 1; |
| 266 | 267 | } |
| 267 | 268 | |
| 268 | 269 | /* |
| 269 | 270 | ** Helper for collecting filename/check-in request parameters. |
| @@ -370,10 +371,37 @@ | ||
| 370 | 371 | } |
| 371 | 372 | if(zRenderMode!=0){ |
| 372 | 373 | cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode); |
| 373 | 374 | } |
| 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 | +} | |
| 375 | 403 | |
| 376 | 404 | #if INTERFACE |
| 377 | 405 | /* |
| 378 | 406 | ** Internal mapping of ajax sub-route names to various metadata. |
| 379 | 407 | */ |
| @@ -380,11 +408,12 @@ | ||
| 380 | 408 | struct AjaxRoute { |
| 381 | 409 | const char *zName; /* Name part of the route after "ajax/" */ |
| 382 | 410 | void (*xCallback)(); /* Impl function for the route. */ |
| 383 | 411 | int bWriteMode; /* True if requires write mode */ |
| 384 | 412 | int bPost; /* True if requires POST (i.e. CSRF |
| 385 | - ** verification) */ | |
| 413 | + ** verification). Value is passed to | |
| 414 | + ** cgi_csrf_safe(). */ | |
| 386 | 415 | }; |
| 387 | 416 | typedef struct AjaxRoute AjaxRoute; |
| 388 | 417 | #endif /*INTERFACE*/ |
| 389 | 418 | |
| 390 | 419 | /* |
| @@ -430,11 +459,12 @@ | ||
| 430 | 459 | ** |
| 431 | 460 | ** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898 |
| 432 | 461 | ** |
| 433 | 462 | ** This particular route is used by /fileedit and /chat, whereas |
| 434 | 463 | ** /wikiedit uses a simpler wiki-specific route. |
| 435 | - */ } | |
| 464 | + */ }, | |
| 465 | + {"artifact.json", ajax_route_artifact_json, 0, 0} | |
| 436 | 466 | }; |
| 437 | 467 | |
| 438 | 468 | if(zName==0 || zName[0]==0){ |
| 439 | 469 | ajax_route_error(400,"Missing required [route] 'name' parameter."); |
| 440 | 470 | return; |
| 441 | 471 |
| --- 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 |