Fossil SCM

If /attachadd is invoked with target=ID instead of a type-specific typeX=ID, behave like /attachaddV2 so that we can incrementally migrate /attachadd links to the v2 behavior by changing the type-specific argument to target=ID. When invoked via older links, /attachadd behaves like it always has, including the single-file attachment form (the plan is to phase that out). There's still some work to do regarding telling it where to redirect to after saving.

stephan 2026-06-03 12:24 UTC attach-v2
Commit 0f071d0f888ab5fbf7e27886e4d57e11f4d07afb5fc5f2ee152977d65c5a29c3
+34 -11
--- src/attach.c
+++ src/attach.c
@@ -407,33 +407,53 @@
407407
** page=WIKIPAGE
408408
** technote=HASH
409409
** forumpost=HASH
410410
** from=URL
411411
**
412
+** Or:
413
+**
414
+** target=ATTACHMENT_TARGET
415
+**
416
+** Behaves exactly like /attachaddV2.
417
+**
412418
*/
413419
void attachadd_page(void){
414
- const char *zPage = P("page");
415
- const char *zForumPost = P("forumpost");
416
- const char *zTkt = P("tkt");
417
- const char *zTechNote = P("technote");
418
- const char *zFrom = P("from");
419
- const char *aContent = P("f");
420
- const char *zName = PD("f:filename","unknown");
421
- const char *zComment = PD("comment", "");
420
+ const char *zPage;
421
+ const char *zForumPost;
422
+ const char *zTkt;
423
+ const char *zTechNote;
424
+ const char *zFrom;
425
+ const char *aContent;
426
+ const char *zName;
427
+ const char *zComment;
422428
const char *zTarget;
423429
char * zTo = 0;
424430
char *zTargetType = 0;
425431
char *zExtraFree = 0;
426
- int szContent = atoi(PD("f:bytes","0"));
432
+ int szContent;
427433
int goodCaptcha = 1;
428434
int szLimit = 0;
429435
436
+ if( P("target")!=0 ){
437
+ attachaddV2_page();
438
+ return;
439
+ }
440
+ zPage = P("page");
441
+ zForumPost = P("forumpost");
442
+ zTkt = P("tkt");
443
+ zTechNote = P("technote");
444
+ zFrom = P("from");
445
+ aContent = P("f");
446
+ zName = PD("f:filename","unknown");
447
+ zComment = PD("comment", "");
448
+ szContent = atoi(PD("f:bytes","0"));
449
+
430450
if( zFrom==0 ) zFrom = mprintf("%R/home");
431451
if( P("cancel") ) cgi_redirect(zFrom);
432452
if( (!!zPage + !!zTkt + !!zTechNote + !!zForumPost)!=1 ){
433453
webpage_error("Requires exactly one one: page=X, tkt=X, forumpost=X,"
434
- " or technote=X");
454
+ " technote=X, or target=X");
435455
}
436456
login_check_credentials();
437457
if( zForumPost ){
438458
int fpid;
439459
if( g.perm.AttachForum==0 ){
@@ -559,11 +579,11 @@
559579
** Responds with JSON: an empty object on success and
560580
** {error:"message"} on error. The on-success response structure is
561581
** subject to amendment.
562582
*/
563583
void attachaddV2_ajax_post(void){
564
- const char *zTarget = P("target");
584
+ const char *zTarget;
565585
char *zExtraFree = 0;
566586
int iTgtType = 0;
567587
int bNeedsModeration = 0;
568588
int i;
569589
int goodCaptcha = 1;
@@ -571,16 +591,18 @@
571591
int bRollback = 0; /* Roll back if true. */
572592
char aKeyPrefix[20]; /* Buffer for key "file%d" */
573593
char aKeySize[30]; /* Buffer for key "file%d:bytes" */
574594
char aKeyName[30]; /* Buffer for key "file%d:filename" */
575595
char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
596
+
576597
if( ! ajax_route_bootstrap(0, 1) ){
577598
return;
578599
}else if( !(goodCaptcha = captcha_is_correct(0)) ){
579600
goto ajax_post_403;
580601
}
581602
db_begin_transaction();
603
+ zTarget = P("target");
582604
iTgtType = attachment_target_type(zTarget);
583605
switch( iTgtType ){
584606
default:
585607
case 0:
586608
ajax_route_error(400, "Invalid attachment target.");
@@ -828,10 +850,11 @@
828850
/* Form gets fleshed out and activate from fossil.attach.js. */
829851
@ <div id='attachadd-form-wrapper'>
830852
@ <input type="hidden" name="target" value="%h(zTarget)">
831853
@ <input type="hidden" name="from" value="%h(zFrom)">
832854
captcha_generate(0);
855
+ login_insert_csrf_secret();
833856
@ </div>
834857
builtin_fossil_js_bundle_or("attach", NULL);
835858
db_end_transaction(0);
836859
style_finish_page();
837860
fossil_free(zTargetType);
838861
--- src/attach.c
+++ src/attach.c
@@ -407,33 +407,53 @@
407 ** page=WIKIPAGE
408 ** technote=HASH
409 ** forumpost=HASH
410 ** from=URL
411 **
 
 
 
 
 
 
412 */
413 void attachadd_page(void){
414 const char *zPage = P("page");
415 const char *zForumPost = P("forumpost");
416 const char *zTkt = P("tkt");
417 const char *zTechNote = P("technote");
418 const char *zFrom = P("from");
419 const char *aContent = P("f");
420 const char *zName = PD("f:filename","unknown");
421 const char *zComment = PD("comment", "");
422 const char *zTarget;
423 char * zTo = 0;
424 char *zTargetType = 0;
425 char *zExtraFree = 0;
426 int szContent = atoi(PD("f:bytes","0"));
427 int goodCaptcha = 1;
428 int szLimit = 0;
429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430 if( zFrom==0 ) zFrom = mprintf("%R/home");
431 if( P("cancel") ) cgi_redirect(zFrom);
432 if( (!!zPage + !!zTkt + !!zTechNote + !!zForumPost)!=1 ){
433 webpage_error("Requires exactly one one: page=X, tkt=X, forumpost=X,"
434 " or technote=X");
435 }
436 login_check_credentials();
437 if( zForumPost ){
438 int fpid;
439 if( g.perm.AttachForum==0 ){
@@ -559,11 +579,11 @@
559 ** Responds with JSON: an empty object on success and
560 ** {error:"message"} on error. The on-success response structure is
561 ** subject to amendment.
562 */
563 void attachaddV2_ajax_post(void){
564 const char *zTarget = P("target");
565 char *zExtraFree = 0;
566 int iTgtType = 0;
567 int bNeedsModeration = 0;
568 int i;
569 int goodCaptcha = 1;
@@ -571,16 +591,18 @@
571 int bRollback = 0; /* Roll back if true. */
572 char aKeyPrefix[20]; /* Buffer for key "file%d" */
573 char aKeySize[30]; /* Buffer for key "file%d:bytes" */
574 char aKeyName[30]; /* Buffer for key "file%d:filename" */
575 char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
 
576 if( ! ajax_route_bootstrap(0, 1) ){
577 return;
578 }else if( !(goodCaptcha = captcha_is_correct(0)) ){
579 goto ajax_post_403;
580 }
581 db_begin_transaction();
 
582 iTgtType = attachment_target_type(zTarget);
583 switch( iTgtType ){
584 default:
585 case 0:
586 ajax_route_error(400, "Invalid attachment target.");
@@ -828,10 +850,11 @@
828 /* Form gets fleshed out and activate from fossil.attach.js. */
829 @ <div id='attachadd-form-wrapper'>
830 @ <input type="hidden" name="target" value="%h(zTarget)">
831 @ <input type="hidden" name="from" value="%h(zFrom)">
832 captcha_generate(0);
 
833 @ </div>
834 builtin_fossil_js_bundle_or("attach", NULL);
835 db_end_transaction(0);
836 style_finish_page();
837 fossil_free(zTargetType);
838
--- src/attach.c
+++ src/attach.c
@@ -407,33 +407,53 @@
407 ** page=WIKIPAGE
408 ** technote=HASH
409 ** forumpost=HASH
410 ** from=URL
411 **
412 ** Or:
413 **
414 ** target=ATTACHMENT_TARGET
415 **
416 ** Behaves exactly like /attachaddV2.
417 **
418 */
419 void attachadd_page(void){
420 const char *zPage;
421 const char *zForumPost;
422 const char *zTkt;
423 const char *zTechNote;
424 const char *zFrom;
425 const char *aContent;
426 const char *zName;
427 const char *zComment;
428 const char *zTarget;
429 char * zTo = 0;
430 char *zTargetType = 0;
431 char *zExtraFree = 0;
432 int szContent;
433 int goodCaptcha = 1;
434 int szLimit = 0;
435
436 if( P("target")!=0 ){
437 attachaddV2_page();
438 return;
439 }
440 zPage = P("page");
441 zForumPost = P("forumpost");
442 zTkt = P("tkt");
443 zTechNote = P("technote");
444 zFrom = P("from");
445 aContent = P("f");
446 zName = PD("f:filename","unknown");
447 zComment = PD("comment", "");
448 szContent = atoi(PD("f:bytes","0"));
449
450 if( zFrom==0 ) zFrom = mprintf("%R/home");
451 if( P("cancel") ) cgi_redirect(zFrom);
452 if( (!!zPage + !!zTkt + !!zTechNote + !!zForumPost)!=1 ){
453 webpage_error("Requires exactly one one: page=X, tkt=X, forumpost=X,"
454 " technote=X, or target=X");
455 }
456 login_check_credentials();
457 if( zForumPost ){
458 int fpid;
459 if( g.perm.AttachForum==0 ){
@@ -559,11 +579,11 @@
579 ** Responds with JSON: an empty object on success and
580 ** {error:"message"} on error. The on-success response structure is
581 ** subject to amendment.
582 */
583 void attachaddV2_ajax_post(void){
584 const char *zTarget;
585 char *zExtraFree = 0;
586 int iTgtType = 0;
587 int bNeedsModeration = 0;
588 int i;
589 int goodCaptcha = 1;
@@ -571,16 +591,18 @@
591 int bRollback = 0; /* Roll back if true. */
592 char aKeyPrefix[20]; /* Buffer for key "file%d" */
593 char aKeySize[30]; /* Buffer for key "file%d:bytes" */
594 char aKeyName[30]; /* Buffer for key "file%d:filename" */
595 char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
596
597 if( ! ajax_route_bootstrap(0, 1) ){
598 return;
599 }else if( !(goodCaptcha = captcha_is_correct(0)) ){
600 goto ajax_post_403;
601 }
602 db_begin_transaction();
603 zTarget = P("target");
604 iTgtType = attachment_target_type(zTarget);
605 switch( iTgtType ){
606 default:
607 case 0:
608 ajax_route_error(400, "Invalid attachment target.");
@@ -828,10 +850,11 @@
850 /* Form gets fleshed out and activate from fossil.attach.js. */
851 @ <div id='attachadd-form-wrapper'>
852 @ <input type="hidden" name="target" value="%h(zTarget)">
853 @ <input type="hidden" name="from" value="%h(zFrom)">
854 captcha_generate(0);
855 login_insert_csrf_secret();
856 @ </div>
857 builtin_fossil_js_bundle_or("attach", NULL);
858 db_end_transaction(0);
859 style_finish_page();
860 fossil_free(zTargetType);
861
+1 -1
--- src/forum.c
+++ src/forum.c
@@ -1279,11 +1279,11 @@
12791279
&& forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
12801280
/* When an admin edits someone else's post, the admin
12811281
** effectively takes over ownership of it (and we currently
12821282
** have no way of passing it back). Because of this, we
12831283
** check the ownership of `p` instead of `pHead`. */
1284
- @ <form method="post" action="%R/attachaddV2" \
1284
+ @ <form method="post" action="%R/attachadd" \
12851285
@ class='file-attach'>\
12861286
@ <input type="hidden" name="target" value="%T(pHead->zUuid)">
12871287
@ <input type="submit" value="Attach...">
12881288
login_insert_csrf_secret();
12891289
moderation_pending_www(p->fpid);
12901290
--- src/forum.c
+++ src/forum.c
@@ -1279,11 +1279,11 @@
1279 && forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
1280 /* When an admin edits someone else's post, the admin
1281 ** effectively takes over ownership of it (and we currently
1282 ** have no way of passing it back). Because of this, we
1283 ** check the ownership of `p` instead of `pHead`. */
1284 @ <form method="post" action="%R/attachaddV2" \
1285 @ class='file-attach'>\
1286 @ <input type="hidden" name="target" value="%T(pHead->zUuid)">
1287 @ <input type="submit" value="Attach...">
1288 login_insert_csrf_secret();
1289 moderation_pending_www(p->fpid);
1290
--- src/forum.c
+++ src/forum.c
@@ -1279,11 +1279,11 @@
1279 && forumpost_is_owner(p/*not pHead*/->fpid, 0)) ){
1280 /* When an admin edits someone else's post, the admin
1281 ** effectively takes over ownership of it (and we currently
1282 ** have no way of passing it back). Because of this, we
1283 ** check the ownership of `p` instead of `pHead`. */
1284 @ <form method="post" action="%R/attachadd" \
1285 @ class='file-attach'>\
1286 @ <input type="hidden" name="target" value="%T(pHead->zUuid)">
1287 @ <input type="submit" value="Attach...">
1288 login_insert_csrf_secret();
1289 moderation_pending_www(p->fpid);
1290
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -467,14 +467,14 @@
467467
return i;
468468
}
469469
}/*Attacher*/;
470470
F.Attacher = Attacher;
471471
472
- if( document.body.classList.contains('cpage-attachaddV2') ){
472
+ const eFormDiv = document.querySelector('#attachadd-form-wrapper');
473
+ if( eFormDiv ){
473474
const urlArgs = new URLSearchParams(window.location.search);
474475
let zTarget = urlArgs.get('target');
475
- const eFormDiv = document.querySelector('#attachadd-form-wrapper');
476476
const eBtnSubmit = D.button("Submit");
477477
eBtnSubmit.type = 'button';
478478
const updateBtnSubmit = (attacher)=>{
479479
if( attacher.isPopulated ){
480480
eBtnSubmit.removeAttribute('disabled');
481481
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -467,14 +467,14 @@
467 return i;
468 }
469 }/*Attacher*/;
470 F.Attacher = Attacher;
471
472 if( document.body.classList.contains('cpage-attachaddV2') ){
 
473 const urlArgs = new URLSearchParams(window.location.search);
474 let zTarget = urlArgs.get('target');
475 const eFormDiv = document.querySelector('#attachadd-form-wrapper');
476 const eBtnSubmit = D.button("Submit");
477 eBtnSubmit.type = 'button';
478 const updateBtnSubmit = (attacher)=>{
479 if( attacher.isPopulated ){
480 eBtnSubmit.removeAttribute('disabled');
481
--- src/fossil.attach.js
+++ src/fossil.attach.js
@@ -467,14 +467,14 @@
467 return i;
468 }
469 }/*Attacher*/;
470 F.Attacher = Attacher;
471
472 const eFormDiv = document.querySelector('#attachadd-form-wrapper');
473 if( eFormDiv ){
474 const urlArgs = new URLSearchParams(window.location.search);
475 let zTarget = urlArgs.get('target');
 
476 const eBtnSubmit = D.button("Submit");
477 eBtnSubmit.type = 'button';
478 const updateBtnSubmit = (attacher)=>{
479 if( attacher.isPopulated ){
480 eBtnSubmit.removeAttribute('disabled');
481

Keyboard Shortcuts

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