Fossil SCM
Re-add CSRF check when posting attachments. Touchups in the post-attach redirection support. Fix a mem leak in attach_commit(). Make whether to show the description field an option to the attacher widget constructor and hide the description fields for now because we apparently do nothing with them.
Commit
059760519fb0a26b60b2ebe52b7645a0be264391ed31708cc1b55263a6020d8f
Parent
0f071d0f888ab5f…
2 files changed
+18
-32
+35
-19
+18
-32
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -388,11 +388,11 @@ | ||
| 388 | 388 | if( n>0 ){ |
| 389 | 389 | blob_appendf(&manifest, "C %#F\n", n, zComment); |
| 390 | 390 | } |
| 391 | 391 | } |
| 392 | 392 | zDate = date_in_standard_format("now"); |
| 393 | - blob_appendf(&manifest, "D %s\n", zDate); | |
| 393 | + blob_appendf(&manifest, "D %z\n", zDate); | |
| 394 | 394 | blob_appendf(&manifest, "U %F\n", login_name()); |
| 395 | 395 | md5sum_blob(&manifest, &cksum); |
| 396 | 396 | blob_appendf(&manifest, "Z %b\n", &cksum); |
| 397 | 397 | attach_put(&manifest, rid, needModerator); |
| 398 | 398 | assert( blob_is_reset(&manifest) ); |
| @@ -424,11 +424,11 @@ | ||
| 424 | 424 | const char *zFrom; |
| 425 | 425 | const char *aContent; |
| 426 | 426 | const char *zName; |
| 427 | 427 | const char *zComment; |
| 428 | 428 | const char *zTarget; |
| 429 | - char * zTo = 0; | |
| 429 | + char * zTo = 0; /* Optionally redirect here after saving */ | |
| 430 | 430 | char *zTargetType = 0; |
| 431 | 431 | char *zExtraFree = 0; |
| 432 | 432 | int szContent; |
| 433 | 433 | int goodCaptcha = 1; |
| 434 | 434 | int szLimit = 0; |
| @@ -564,11 +564,11 @@ | ||
| 564 | 564 | } |
| 565 | 565 | |
| 566 | 566 | /* |
| 567 | 567 | ** WEBPAGE: attachaddV2_ajax_post hidden |
| 568 | 568 | ** |
| 569 | -** Requires a POST request with: | |
| 569 | +** Used by /attachaddV2 to handle attachments via POST requests with: | |
| 570 | 570 | ** |
| 571 | 571 | ** target=ATTACHMENT_TARGET |
| 572 | 572 | ** file1..fileN=FILE_OBJECTS |
| 573 | 573 | ** dryrun=0|1 |
| 574 | 574 | ** |
| @@ -596,10 +596,13 @@ | ||
| 596 | 596 | |
| 597 | 597 | if( ! ajax_route_bootstrap(0, 1) ){ |
| 598 | 598 | return; |
| 599 | 599 | }else if( !(goodCaptcha = captcha_is_correct(0)) ){ |
| 600 | 600 | goto ajax_post_403; |
| 601 | + }else if( !cgi_csrf_safe(2) ){ | |
| 602 | + ajax_route_error(403, "Invalid CSRF signature."); | |
| 603 | + return; | |
| 601 | 604 | } |
| 602 | 605 | db_begin_transaction(); |
| 603 | 606 | zTarget = P("target"); |
| 604 | 607 | iTgtType = attachment_target_type(zTarget); |
| 605 | 608 | switch( iTgtType ){ |
| @@ -716,18 +719,25 @@ | ||
| 716 | 719 | return; |
| 717 | 720 | } |
| 718 | 721 | |
| 719 | 722 | /* |
| 720 | 723 | ** WEBPAGE: attachaddV2 hidden |
| 721 | -** Add a new attachment. | |
| 724 | +** | |
| 725 | +** Lists attachments for, and can add them to, a target artifact. | |
| 722 | 726 | ** |
| 723 | 727 | ** target=TKT_HASH|WIKIPAGE_NAME|TECHNOTE_HASH|FORUMPOST_HASH |
| 724 | -** from=URL | |
| 728 | +** from=ORIGINATING_URL | |
| 729 | +** to=URL_ON_COMPLETION | |
| 725 | 730 | ** |
| 726 | 731 | ** Works like /attachadd but uses a JS-based interactive attachment |
| 727 | 732 | ** selector. |
| 728 | 733 | ** |
| 734 | +** from=X and to=X tell it how to redirect when it's done. to=X | |
| 735 | +** overrides from=X. If neither is set, it will redirect back to this | |
| 736 | +** page to render the updated attachment list. | |
| 737 | +** | |
| 738 | +** This page requires a post-2020 JS-capable browser. | |
| 729 | 739 | */ |
| 730 | 740 | void attachaddV2_page(void){ |
| 731 | 741 | const char *zFrom = P("from"); |
| 732 | 742 | const char *zTarget = P("target"); |
| 733 | 743 | char *zTo = 0; |
| @@ -734,12 +744,10 @@ | ||
| 734 | 744 | char *zTargetType = 0; |
| 735 | 745 | char *zExtraFree = 0; |
| 736 | 746 | int iTgtType = 0; |
| 737 | 747 | int szContent = 0; |
| 738 | 748 | int goodCaptcha = 1; |
| 739 | - int szLimit = 0; | |
| 740 | - int bNeedsModeration = 0; | |
| 741 | 749 | |
| 742 | 750 | if( zFrom==0 ) zFrom = mprintf("%R/home"); |
| 743 | 751 | if( P("cancel") ) cgi_redirect(zFrom); |
| 744 | 752 | if( 0==zTarget ){ |
| 745 | 753 | webpage_error("Requires target=X"); |
| @@ -766,11 +774,10 @@ | ||
| 766 | 774 | } |
| 767 | 775 | zTarget = zExtraFree = rid_to_uuid(fpid); |
| 768 | 776 | zTargetType = mprintf("Forum post <a href=\"%R/forumpost/%S\">%.16h</a>", |
| 769 | 777 | zTarget, zTarget); |
| 770 | 778 | zTo = mprintf("%R/forumpost/%S", zTarget); |
| 771 | - bNeedsModeration = forum_need_moderation(); | |
| 772 | 779 | break; |
| 773 | 780 | } |
| 774 | 781 | case CFTYPE_EVENT:{ |
| 775 | 782 | if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 776 | 783 | login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach); |
| @@ -781,11 +788,10 @@ | ||
| 781 | 788 | " WHERE tagname GLOB 'event-%q*'", zTarget); |
| 782 | 789 | if( zTarget==0) fossil_redirect_home(); |
| 783 | 790 | } |
| 784 | 791 | zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>", |
| 785 | 792 | zTarget, zTarget); |
| 786 | - bNeedsModeration = 0; | |
| 787 | 793 | break; |
| 788 | 794 | } |
| 789 | 795 | case CFTYPE_TICKET:{ |
| 790 | 796 | if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ |
| 791 | 797 | login_needed(g.anon.ApndTkt && g.anon.Attach); |
| @@ -796,11 +802,10 @@ | ||
| 796 | 802 | " WHERE tagname GLOB 'tkt-%q*'", zTarget); |
| 797 | 803 | if( zTarget==0 ) fossil_redirect_home(); |
| 798 | 804 | } |
| 799 | 805 | zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", |
| 800 | 806 | zTarget, zTarget); |
| 801 | - bNeedsModeration = ticket_need_moderation(0); | |
| 802 | 807 | break; |
| 803 | 808 | } |
| 804 | 809 | case CFTYPE_WIKI:{ |
| 805 | 810 | if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 806 | 811 | login_needed(g.anon.ApndWiki && g.anon.Attach); |
| @@ -809,37 +814,15 @@ | ||
| 809 | 814 | if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zTarget) ){ |
| 810 | 815 | fossil_redirect_home(); |
| 811 | 816 | } |
| 812 | 817 | zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", |
| 813 | 818 | zTarget, zTarget); |
| 814 | - bNeedsModeration = wiki_need_moderation(0); | |
| 815 | 819 | break; |
| 816 | 820 | } |
| 817 | 821 | } |
| 818 | 822 | |
| 819 | 823 | db_begin_transaction(); |
| 820 | -#if 0 | |
| 821 | - szLimit = db_get_int("attachment-size-limit", 0); | |
| 822 | - if( szContent<0 || (szLimit && szContent>szLimit) ){ | |
| 823 | - /* This check must be done late so that zTargetType is set up. */ | |
| 824 | - @ <p class="generalError">Attachment %h(zName) is too large. | |
| 825 | - @ <a href="%R/help/attachment-size-limit">Limit</a> is | |
| 826 | - @ %d(szLimit ? szLimit : 0x7fffffff) bytes</p> | |
| 827 | - /* Fall through and render form. */ | |
| 828 | - }else if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){ | |
| 829 | -#if 0 | |
| 830 | - attach_commit(zName, zTarget, aContent, szContent, | |
| 831 | - bNeedsModeration, zComment); | |
| 832 | -#endif | |
| 833 | - cgi_redirect(zTo ? zTo : zFrom); | |
| 834 | - } | |
| 835 | -#else | |
| 836 | - (void)bNeedsModeration; | |
| 837 | - (void)szLimit; | |
| 838 | - (void)szContent; | |
| 839 | - (void)zTo; | |
| 840 | -#endif | |
| 841 | 824 | |
| 842 | 825 | style_set_current_feature("attach"); |
| 843 | 826 | style_header("Add Attachment"); |
| 844 | 827 | if( !goodCaptcha ){ |
| 845 | 828 | @ <p class="generalError">Error: Incorrect security code.</p> |
| @@ -849,10 +832,13 @@ | ||
| 849 | 832 | ATTACHLIST_SIZE | ATTACHLIST_HIDE_UNAPPROVED); |
| 850 | 833 | /* Form gets fleshed out and activate from fossil.attach.js. */ |
| 851 | 834 | @ <div id='attachadd-form-wrapper'> |
| 852 | 835 | @ <input type="hidden" name="target" value="%h(zTarget)"> |
| 853 | 836 | @ <input type="hidden" name="from" value="%h(zFrom)"> |
| 837 | + if( zTo ){ | |
| 838 | + @ <input type="hidden" name="to" value="%h(zTo)"> | |
| 839 | + } | |
| 854 | 840 | captcha_generate(0); |
| 855 | 841 | login_insert_csrf_secret(); |
| 856 | 842 | @ </div> |
| 857 | 843 | builtin_fossil_js_bundle_or("attach", NULL); |
| 858 | 844 | db_end_transaction(0); |
| 859 | 845 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -388,11 +388,11 @@ | |
| 388 | if( n>0 ){ |
| 389 | blob_appendf(&manifest, "C %#F\n", n, zComment); |
| 390 | } |
| 391 | } |
| 392 | zDate = date_in_standard_format("now"); |
| 393 | blob_appendf(&manifest, "D %s\n", zDate); |
| 394 | blob_appendf(&manifest, "U %F\n", login_name()); |
| 395 | md5sum_blob(&manifest, &cksum); |
| 396 | blob_appendf(&manifest, "Z %b\n", &cksum); |
| 397 | attach_put(&manifest, rid, needModerator); |
| 398 | assert( blob_is_reset(&manifest) ); |
| @@ -424,11 +424,11 @@ | |
| 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; |
| @@ -564,11 +564,11 @@ | |
| 564 | } |
| 565 | |
| 566 | /* |
| 567 | ** WEBPAGE: attachaddV2_ajax_post hidden |
| 568 | ** |
| 569 | ** Requires a POST request with: |
| 570 | ** |
| 571 | ** target=ATTACHMENT_TARGET |
| 572 | ** file1..fileN=FILE_OBJECTS |
| 573 | ** dryrun=0|1 |
| 574 | ** |
| @@ -596,10 +596,13 @@ | |
| 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 ){ |
| @@ -716,18 +719,25 @@ | |
| 716 | return; |
| 717 | } |
| 718 | |
| 719 | /* |
| 720 | ** WEBPAGE: attachaddV2 hidden |
| 721 | ** Add a new attachment. |
| 722 | ** |
| 723 | ** target=TKT_HASH|WIKIPAGE_NAME|TECHNOTE_HASH|FORUMPOST_HASH |
| 724 | ** from=URL |
| 725 | ** |
| 726 | ** Works like /attachadd but uses a JS-based interactive attachment |
| 727 | ** selector. |
| 728 | ** |
| 729 | */ |
| 730 | void attachaddV2_page(void){ |
| 731 | const char *zFrom = P("from"); |
| 732 | const char *zTarget = P("target"); |
| 733 | char *zTo = 0; |
| @@ -734,12 +744,10 @@ | |
| 734 | char *zTargetType = 0; |
| 735 | char *zExtraFree = 0; |
| 736 | int iTgtType = 0; |
| 737 | int szContent = 0; |
| 738 | int goodCaptcha = 1; |
| 739 | int szLimit = 0; |
| 740 | int bNeedsModeration = 0; |
| 741 | |
| 742 | if( zFrom==0 ) zFrom = mprintf("%R/home"); |
| 743 | if( P("cancel") ) cgi_redirect(zFrom); |
| 744 | if( 0==zTarget ){ |
| 745 | webpage_error("Requires target=X"); |
| @@ -766,11 +774,10 @@ | |
| 766 | } |
| 767 | zTarget = zExtraFree = rid_to_uuid(fpid); |
| 768 | zTargetType = mprintf("Forum post <a href=\"%R/forumpost/%S\">%.16h</a>", |
| 769 | zTarget, zTarget); |
| 770 | zTo = mprintf("%R/forumpost/%S", zTarget); |
| 771 | bNeedsModeration = forum_need_moderation(); |
| 772 | break; |
| 773 | } |
| 774 | case CFTYPE_EVENT:{ |
| 775 | if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 776 | login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach); |
| @@ -781,11 +788,10 @@ | |
| 781 | " WHERE tagname GLOB 'event-%q*'", zTarget); |
| 782 | if( zTarget==0) fossil_redirect_home(); |
| 783 | } |
| 784 | zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>", |
| 785 | zTarget, zTarget); |
| 786 | bNeedsModeration = 0; |
| 787 | break; |
| 788 | } |
| 789 | case CFTYPE_TICKET:{ |
| 790 | if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ |
| 791 | login_needed(g.anon.ApndTkt && g.anon.Attach); |
| @@ -796,11 +802,10 @@ | |
| 796 | " WHERE tagname GLOB 'tkt-%q*'", zTarget); |
| 797 | if( zTarget==0 ) fossil_redirect_home(); |
| 798 | } |
| 799 | zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", |
| 800 | zTarget, zTarget); |
| 801 | bNeedsModeration = ticket_need_moderation(0); |
| 802 | break; |
| 803 | } |
| 804 | case CFTYPE_WIKI:{ |
| 805 | if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 806 | login_needed(g.anon.ApndWiki && g.anon.Attach); |
| @@ -809,37 +814,15 @@ | |
| 809 | if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zTarget) ){ |
| 810 | fossil_redirect_home(); |
| 811 | } |
| 812 | zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", |
| 813 | zTarget, zTarget); |
| 814 | bNeedsModeration = wiki_need_moderation(0); |
| 815 | break; |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | db_begin_transaction(); |
| 820 | #if 0 |
| 821 | szLimit = db_get_int("attachment-size-limit", 0); |
| 822 | if( szContent<0 || (szLimit && szContent>szLimit) ){ |
| 823 | /* This check must be done late so that zTargetType is set up. */ |
| 824 | @ <p class="generalError">Attachment %h(zName) is too large. |
| 825 | @ <a href="%R/help/attachment-size-limit">Limit</a> is |
| 826 | @ %d(szLimit ? szLimit : 0x7fffffff) bytes</p> |
| 827 | /* Fall through and render form. */ |
| 828 | }else if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){ |
| 829 | #if 0 |
| 830 | attach_commit(zName, zTarget, aContent, szContent, |
| 831 | bNeedsModeration, zComment); |
| 832 | #endif |
| 833 | cgi_redirect(zTo ? zTo : zFrom); |
| 834 | } |
| 835 | #else |
| 836 | (void)bNeedsModeration; |
| 837 | (void)szLimit; |
| 838 | (void)szContent; |
| 839 | (void)zTo; |
| 840 | #endif |
| 841 | |
| 842 | style_set_current_feature("attach"); |
| 843 | style_header("Add Attachment"); |
| 844 | if( !goodCaptcha ){ |
| 845 | @ <p class="generalError">Error: Incorrect security code.</p> |
| @@ -849,10 +832,13 @@ | |
| 849 | ATTACHLIST_SIZE | ATTACHLIST_HIDE_UNAPPROVED); |
| 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 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -388,11 +388,11 @@ | |
| 388 | if( n>0 ){ |
| 389 | blob_appendf(&manifest, "C %#F\n", n, zComment); |
| 390 | } |
| 391 | } |
| 392 | zDate = date_in_standard_format("now"); |
| 393 | blob_appendf(&manifest, "D %z\n", zDate); |
| 394 | blob_appendf(&manifest, "U %F\n", login_name()); |
| 395 | md5sum_blob(&manifest, &cksum); |
| 396 | blob_appendf(&manifest, "Z %b\n", &cksum); |
| 397 | attach_put(&manifest, rid, needModerator); |
| 398 | assert( blob_is_reset(&manifest) ); |
| @@ -424,11 +424,11 @@ | |
| 424 | const char *zFrom; |
| 425 | const char *aContent; |
| 426 | const char *zName; |
| 427 | const char *zComment; |
| 428 | const char *zTarget; |
| 429 | char * zTo = 0; /* Optionally redirect here after saving */ |
| 430 | char *zTargetType = 0; |
| 431 | char *zExtraFree = 0; |
| 432 | int szContent; |
| 433 | int goodCaptcha = 1; |
| 434 | int szLimit = 0; |
| @@ -564,11 +564,11 @@ | |
| 564 | } |
| 565 | |
| 566 | /* |
| 567 | ** WEBPAGE: attachaddV2_ajax_post hidden |
| 568 | ** |
| 569 | ** Used by /attachaddV2 to handle attachments via POST requests with: |
| 570 | ** |
| 571 | ** target=ATTACHMENT_TARGET |
| 572 | ** file1..fileN=FILE_OBJECTS |
| 573 | ** dryrun=0|1 |
| 574 | ** |
| @@ -596,10 +596,13 @@ | |
| 596 | |
| 597 | if( ! ajax_route_bootstrap(0, 1) ){ |
| 598 | return; |
| 599 | }else if( !(goodCaptcha = captcha_is_correct(0)) ){ |
| 600 | goto ajax_post_403; |
| 601 | }else if( !cgi_csrf_safe(2) ){ |
| 602 | ajax_route_error(403, "Invalid CSRF signature."); |
| 603 | return; |
| 604 | } |
| 605 | db_begin_transaction(); |
| 606 | zTarget = P("target"); |
| 607 | iTgtType = attachment_target_type(zTarget); |
| 608 | switch( iTgtType ){ |
| @@ -716,18 +719,25 @@ | |
| 719 | return; |
| 720 | } |
| 721 | |
| 722 | /* |
| 723 | ** WEBPAGE: attachaddV2 hidden |
| 724 | ** |
| 725 | ** Lists attachments for, and can add them to, a target artifact. |
| 726 | ** |
| 727 | ** target=TKT_HASH|WIKIPAGE_NAME|TECHNOTE_HASH|FORUMPOST_HASH |
| 728 | ** from=ORIGINATING_URL |
| 729 | ** to=URL_ON_COMPLETION |
| 730 | ** |
| 731 | ** Works like /attachadd but uses a JS-based interactive attachment |
| 732 | ** selector. |
| 733 | ** |
| 734 | ** from=X and to=X tell it how to redirect when it's done. to=X |
| 735 | ** overrides from=X. If neither is set, it will redirect back to this |
| 736 | ** page to render the updated attachment list. |
| 737 | ** |
| 738 | ** This page requires a post-2020 JS-capable browser. |
| 739 | */ |
| 740 | void attachaddV2_page(void){ |
| 741 | const char *zFrom = P("from"); |
| 742 | const char *zTarget = P("target"); |
| 743 | char *zTo = 0; |
| @@ -734,12 +744,10 @@ | |
| 744 | char *zTargetType = 0; |
| 745 | char *zExtraFree = 0; |
| 746 | int iTgtType = 0; |
| 747 | int szContent = 0; |
| 748 | int goodCaptcha = 1; |
| 749 | |
| 750 | if( zFrom==0 ) zFrom = mprintf("%R/home"); |
| 751 | if( P("cancel") ) cgi_redirect(zFrom); |
| 752 | if( 0==zTarget ){ |
| 753 | webpage_error("Requires target=X"); |
| @@ -766,11 +774,10 @@ | |
| 774 | } |
| 775 | zTarget = zExtraFree = rid_to_uuid(fpid); |
| 776 | zTargetType = mprintf("Forum post <a href=\"%R/forumpost/%S\">%.16h</a>", |
| 777 | zTarget, zTarget); |
| 778 | zTo = mprintf("%R/forumpost/%S", zTarget); |
| 779 | break; |
| 780 | } |
| 781 | case CFTYPE_EVENT:{ |
| 782 | if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 783 | login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach); |
| @@ -781,11 +788,10 @@ | |
| 788 | " WHERE tagname GLOB 'event-%q*'", zTarget); |
| 789 | if( zTarget==0) fossil_redirect_home(); |
| 790 | } |
| 791 | zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>", |
| 792 | zTarget, zTarget); |
| 793 | break; |
| 794 | } |
| 795 | case CFTYPE_TICKET:{ |
| 796 | if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ |
| 797 | login_needed(g.anon.ApndTkt && g.anon.Attach); |
| @@ -796,11 +802,10 @@ | |
| 802 | " WHERE tagname GLOB 'tkt-%q*'", zTarget); |
| 803 | if( zTarget==0 ) fossil_redirect_home(); |
| 804 | } |
| 805 | zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", |
| 806 | zTarget, zTarget); |
| 807 | break; |
| 808 | } |
| 809 | case CFTYPE_WIKI:{ |
| 810 | if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ |
| 811 | login_needed(g.anon.ApndWiki && g.anon.Attach); |
| @@ -809,37 +814,15 @@ | |
| 814 | if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zTarget) ){ |
| 815 | fossil_redirect_home(); |
| 816 | } |
| 817 | zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", |
| 818 | zTarget, zTarget); |
| 819 | break; |
| 820 | } |
| 821 | } |
| 822 | |
| 823 | db_begin_transaction(); |
| 824 | |
| 825 | style_set_current_feature("attach"); |
| 826 | style_header("Add Attachment"); |
| 827 | if( !goodCaptcha ){ |
| 828 | @ <p class="generalError">Error: Incorrect security code.</p> |
| @@ -849,10 +832,13 @@ | |
| 832 | ATTACHLIST_SIZE | ATTACHLIST_HIDE_UNAPPROVED); |
| 833 | /* Form gets fleshed out and activate from fossil.attach.js. */ |
| 834 | @ <div id='attachadd-form-wrapper'> |
| 835 | @ <input type="hidden" name="target" value="%h(zTarget)"> |
| 836 | @ <input type="hidden" name="from" value="%h(zFrom)"> |
| 837 | if( zTo ){ |
| 838 | @ <input type="hidden" name="to" value="%h(zTo)"> |
| 839 | } |
| 840 | captcha_generate(0); |
| 841 | login_insert_csrf_secret(); |
| 842 | @ </div> |
| 843 | builtin_fossil_js_bundle_or("attach", NULL); |
| 844 | db_end_transaction(0); |
| 845 |
+35
-19
| --- src/fossil.attach.js | ||
| +++ src/fossil.attach.js | ||
| @@ -36,10 +36,13 @@ | ||
| 36 | 36 | |
| 37 | 37 | opt.startWith[=0]: if >0 then that many file selection widgets |
| 38 | 38 | are automatically activated, as if the user had tapped the Add |
| 39 | 39 | button that many times. As a special case, if this is >0 |
| 40 | 40 | and the user removes the last entry, a new one is added. |
| 41 | + | |
| 42 | + opt.description[=true]: if true then show the file description | |
| 43 | + field, otherwise elide it. | |
| 41 | 44 | |
| 42 | 45 | opt.controls = [array of DOM elements]. Optional DOM elements |
| 43 | 46 | to inject into the UI element which wraps the "Add" button. |
| 44 | 47 | See this.controlsElement. |
| 45 | 48 | |
| @@ -70,11 +73,12 @@ | ||
| 70 | 73 | constructor(opt){ |
| 71 | 74 | this.#opt = opt = F.nu({ |
| 72 | 75 | addButtonLabel: false, |
| 73 | 76 | startWith: 0, |
| 74 | 77 | limit: 0, |
| 75 | - dryRun: false | |
| 78 | + dryRun: false, | |
| 79 | + description: true | |
| 76 | 80 | }, opt); |
| 77 | 81 | this.#e.body = D.addClass(D.div(), 'attach-widget'); |
| 78 | 82 | const eBtnAdd = this.#e.btnAdd = D.addClass( |
| 79 | 83 | D.button(this.#opt.addButtonLabel || 'Add attachment', |
| 80 | 84 | ()=>this.#addRow()), |
| @@ -248,15 +252,17 @@ | ||
| 248 | 252 | "Select/drop file or click the outer border and tap your "+ |
| 249 | 253 | "platform's conventional Paste keyboard shortcut." |
| 250 | 254 | ); |
| 251 | 255 | const eSize = D.addClass(D.span(), 'attach-size'); |
| 252 | 256 | eInfo.append(eFilename, eSize); |
| 253 | - const eDesc = D.addClass( | |
| 254 | - D.attr(D.textarea(), 'placeholder', | |
| 255 | - 'Optional description...'), | |
| 256 | - 'hidden', 'attach-desc' | |
| 257 | - ); | |
| 257 | + const eDesc = this.#opt.description | |
| 258 | + ? D.addClass( | |
| 259 | + D.attr(D.textarea(), 'placeholder', | |
| 260 | + 'Optional description...'), | |
| 261 | + 'hidden', 'attach-desc' | |
| 262 | + ) | |
| 263 | + : undefined; | |
| 258 | 264 | const eRemove = D.addClass( |
| 259 | 265 | D.button('Remove', (ev)=>{ |
| 260 | 266 | ev.stopPropagation(); |
| 261 | 267 | this.#removeRow(rowObj); |
| 262 | 268 | }), |
| @@ -311,11 +317,12 @@ | ||
| 311 | 317 | this.#injestBlob(rowObj, blob); |
| 312 | 318 | break; |
| 313 | 319 | } |
| 314 | 320 | } |
| 315 | 321 | }); |
| 316 | - D.append(eRow, eDropzone, eDesc); | |
| 322 | + eRow.append(eDropzone); | |
| 323 | + if( eDesc ) eRow.append(eDesc); | |
| 317 | 324 | rowObj.e = F.nu({ |
| 318 | 325 | dropzone: eDropzone, |
| 319 | 326 | info: eInfo, |
| 320 | 327 | filename: eFilename, |
| 321 | 328 | size: eSize, |
| @@ -392,16 +399,22 @@ | ||
| 392 | 399 | rowObj.file = file; |
| 393 | 400 | rowObj.mimeType = file.type || 'application/octet-stream'; |
| 394 | 401 | D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content'); |
| 395 | 402 | D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || ''); |
| 396 | 403 | rowObj.e.dropzone.classList.add('populated'); |
| 397 | - rowObj.e.desc.classList.remove('hidden'); | |
| 404 | + if( rowObj.e.desc ){ | |
| 405 | + rowObj.e.desc.classList.remove('hidden'); | |
| 406 | + } | |
| 407 | + if( rowObj.e.thumbnail ){ | |
| 408 | + rowObj.e.thumbnail.remove(); | |
| 409 | + rowObj.e.thumbnail = undefined; | |
| 410 | + } | |
| 398 | 411 | if( file.type?.startsWith?.('image/') || file.type==='BITMAP' ){ |
| 399 | 412 | /* Add a thumbnail */ |
| 400 | - const img = rowObj.e.dropzone.querySelector('img.thumbnail') || D.img(); | |
| 401 | - img.classList.add('thumbnail'); | |
| 413 | + const img = rowObj.e.thumbnail = D.img(); | |
| 402 | 414 | rowObj.e.dropzone.insertBefore(img, rowObj.e.remove); |
| 415 | + img.classList.add('thumbnail'); | |
| 403 | 416 | const reader = new FileReader(); |
| 404 | 417 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 405 | 418 | reader.readAsDataURL(file); |
| 406 | 419 | } |
| 407 | 420 | if( file.size>F.config.attachmentSizeLimit ){ |
| @@ -469,12 +482,14 @@ | ||
| 469 | 482 | }/*Attacher*/; |
| 470 | 483 | F.Attacher = Attacher; |
| 471 | 484 | |
| 472 | 485 | const eFormDiv = document.querySelector('#attachadd-form-wrapper'); |
| 473 | 486 | if( eFormDiv ){ |
| 487 | + /* Inject a file-attachment form. */ | |
| 474 | 488 | const urlArgs = new URLSearchParams(window.location.search); |
| 475 | 489 | let zTarget = urlArgs.get('target'); |
| 490 | + let zTo = urlArgs.get('to') || urlArgs.get('from'); | |
| 476 | 491 | const eBtnSubmit = D.button("Submit"); |
| 477 | 492 | eBtnSubmit.type = 'button'; |
| 478 | 493 | const updateBtnSubmit = (attacher)=>{ |
| 479 | 494 | if( attacher.isPopulated ){ |
| 480 | 495 | eBtnSubmit.removeAttribute('disabled'); |
| @@ -488,11 +503,12 @@ | ||
| 488 | 503 | }; |
| 489 | 504 | const att = new Attacher({ |
| 490 | 505 | container: eFormDiv, |
| 491 | 506 | startWith: 1, |
| 492 | 507 | listener: cbAttacherChange, |
| 493 | - controls: [eBtnSubmit] | |
| 508 | + controls: [eBtnSubmit], | |
| 509 | + description: false | |
| 494 | 510 | }); |
| 495 | 511 | eBtnSubmit.addEventListener('click', async (ev)=>{ |
| 496 | 512 | att.reportError(); |
| 497 | 513 | const li = att.collectState(); |
| 498 | 514 | if( !li.length ) return; |
| @@ -501,16 +517,18 @@ | ||
| 501 | 517 | D.disable(eBtnSubmit); |
| 502 | 518 | const fd = new FormData(); |
| 503 | 519 | let i = 0; |
| 504 | 520 | for(const row of li){ |
| 505 | 521 | ++i; |
| 506 | - fd.append(`file${i}`, row.content); | |
| 507 | - if( row.description ) fd.append(`file${i}_desc`, row.description); | |
| 522 | + fd.append('file'+i, row.content); | |
| 523 | + if( row.description ) fd.append('file'+i+'_desc', row.description); | |
| 508 | 524 | } |
| 509 | 525 | for( const eIn of eFormDiv.querySelectorAll(':scope > input[type="hidden"]') ){ |
| 510 | 526 | if( eIn.name==='target' ){ |
| 511 | 527 | zTarget = eIn.value; |
| 528 | + }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){ | |
| 529 | + zTo = eIn.value; | |
| 512 | 530 | } |
| 513 | 531 | fd.append(eIn.name, eIn.value) |
| 514 | 532 | } |
| 515 | 533 | if( att.isDryRun ){ |
| 516 | 534 | fd.append('dryrun', '1'); |
| @@ -528,23 +546,21 @@ | ||
| 528 | 546 | if( jr?.error || !resp.ok ){ |
| 529 | 547 | const msg = err ? err.message : (jr?.error || resp.statusText); |
| 530 | 548 | att.reportError("Attaching failed: ", msg); |
| 531 | 549 | }else{ |
| 532 | 550 | att.clear(); |
| 533 | - const to = urlArgs.get('to') || urlArgs.get('from') || jr?.redirect; | |
| 551 | + let to = zTo || jr?.redirect; | |
| 534 | 552 | if( to ){ |
| 535 | - if( '/'===to[0] ){ | |
| 536 | - to = F.repoUrl(to.substr(1)); | |
| 553 | + if( '/'!==to[0] ){ | |
| 554 | + to = F.repoUrl(to); | |
| 537 | 555 | } |
| 538 | 556 | window.location = to; |
| 539 | 557 | }else{ |
| 540 | - const tgt = '?target='+zTarget+'&cacheBuster='+Date.now(); | |
| 541 | - //console.error("FIXME: location=",tgt); | |
| 542 | - window.location = tgt; | |
| 558 | + window.location = '?target='+zTarget+'&'+Date.now(); | |
| 543 | 559 | } |
| 544 | 560 | } |
| 545 | 561 | })/*submit handler*/; |
| 546 | 562 | updateBtnSubmit(att); |
| 547 | 563 | F.page.attacher = att /* only for testing via dev console */; |
| 548 | 564 | }/* /attachaddV2 */ |
| 549 | 565 | |
| 550 | 566 | })(window.fossil); |
| 551 | 567 |
| --- src/fossil.attach.js | |
| +++ src/fossil.attach.js | |
| @@ -36,10 +36,13 @@ | |
| 36 | |
| 37 | opt.startWith[=0]: if >0 then that many file selection widgets |
| 38 | are automatically activated, as if the user had tapped the Add |
| 39 | button that many times. As a special case, if this is >0 |
| 40 | and the user removes the last entry, a new one is added. |
| 41 | |
| 42 | opt.controls = [array of DOM elements]. Optional DOM elements |
| 43 | to inject into the UI element which wraps the "Add" button. |
| 44 | See this.controlsElement. |
| 45 | |
| @@ -70,11 +73,12 @@ | |
| 70 | constructor(opt){ |
| 71 | this.#opt = opt = F.nu({ |
| 72 | addButtonLabel: false, |
| 73 | startWith: 0, |
| 74 | limit: 0, |
| 75 | dryRun: false |
| 76 | }, opt); |
| 77 | this.#e.body = D.addClass(D.div(), 'attach-widget'); |
| 78 | const eBtnAdd = this.#e.btnAdd = D.addClass( |
| 79 | D.button(this.#opt.addButtonLabel || 'Add attachment', |
| 80 | ()=>this.#addRow()), |
| @@ -248,15 +252,17 @@ | |
| 248 | "Select/drop file or click the outer border and tap your "+ |
| 249 | "platform's conventional Paste keyboard shortcut." |
| 250 | ); |
| 251 | const eSize = D.addClass(D.span(), 'attach-size'); |
| 252 | eInfo.append(eFilename, eSize); |
| 253 | const eDesc = D.addClass( |
| 254 | D.attr(D.textarea(), 'placeholder', |
| 255 | 'Optional description...'), |
| 256 | 'hidden', 'attach-desc' |
| 257 | ); |
| 258 | const eRemove = D.addClass( |
| 259 | D.button('Remove', (ev)=>{ |
| 260 | ev.stopPropagation(); |
| 261 | this.#removeRow(rowObj); |
| 262 | }), |
| @@ -311,11 +317,12 @@ | |
| 311 | this.#injestBlob(rowObj, blob); |
| 312 | break; |
| 313 | } |
| 314 | } |
| 315 | }); |
| 316 | D.append(eRow, eDropzone, eDesc); |
| 317 | rowObj.e = F.nu({ |
| 318 | dropzone: eDropzone, |
| 319 | info: eInfo, |
| 320 | filename: eFilename, |
| 321 | size: eSize, |
| @@ -392,16 +399,22 @@ | |
| 392 | rowObj.file = file; |
| 393 | rowObj.mimeType = file.type || 'application/octet-stream'; |
| 394 | D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content'); |
| 395 | D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || ''); |
| 396 | rowObj.e.dropzone.classList.add('populated'); |
| 397 | rowObj.e.desc.classList.remove('hidden'); |
| 398 | if( file.type?.startsWith?.('image/') || file.type==='BITMAP' ){ |
| 399 | /* Add a thumbnail */ |
| 400 | const img = rowObj.e.dropzone.querySelector('img.thumbnail') || D.img(); |
| 401 | img.classList.add('thumbnail'); |
| 402 | rowObj.e.dropzone.insertBefore(img, rowObj.e.remove); |
| 403 | const reader = new FileReader(); |
| 404 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 405 | reader.readAsDataURL(file); |
| 406 | } |
| 407 | if( file.size>F.config.attachmentSizeLimit ){ |
| @@ -469,12 +482,14 @@ | |
| 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'); |
| @@ -488,11 +503,12 @@ | |
| 488 | }; |
| 489 | const att = new Attacher({ |
| 490 | container: eFormDiv, |
| 491 | startWith: 1, |
| 492 | listener: cbAttacherChange, |
| 493 | controls: [eBtnSubmit] |
| 494 | }); |
| 495 | eBtnSubmit.addEventListener('click', async (ev)=>{ |
| 496 | att.reportError(); |
| 497 | const li = att.collectState(); |
| 498 | if( !li.length ) return; |
| @@ -501,16 +517,18 @@ | |
| 501 | D.disable(eBtnSubmit); |
| 502 | const fd = new FormData(); |
| 503 | let i = 0; |
| 504 | for(const row of li){ |
| 505 | ++i; |
| 506 | fd.append(`file${i}`, row.content); |
| 507 | if( row.description ) fd.append(`file${i}_desc`, row.description); |
| 508 | } |
| 509 | for( const eIn of eFormDiv.querySelectorAll(':scope > input[type="hidden"]') ){ |
| 510 | if( eIn.name==='target' ){ |
| 511 | zTarget = eIn.value; |
| 512 | } |
| 513 | fd.append(eIn.name, eIn.value) |
| 514 | } |
| 515 | if( att.isDryRun ){ |
| 516 | fd.append('dryrun', '1'); |
| @@ -528,23 +546,21 @@ | |
| 528 | if( jr?.error || !resp.ok ){ |
| 529 | const msg = err ? err.message : (jr?.error || resp.statusText); |
| 530 | att.reportError("Attaching failed: ", msg); |
| 531 | }else{ |
| 532 | att.clear(); |
| 533 | const to = urlArgs.get('to') || urlArgs.get('from') || jr?.redirect; |
| 534 | if( to ){ |
| 535 | if( '/'===to[0] ){ |
| 536 | to = F.repoUrl(to.substr(1)); |
| 537 | } |
| 538 | window.location = to; |
| 539 | }else{ |
| 540 | const tgt = '?target='+zTarget+'&cacheBuster='+Date.now(); |
| 541 | //console.error("FIXME: location=",tgt); |
| 542 | window.location = tgt; |
| 543 | } |
| 544 | } |
| 545 | })/*submit handler*/; |
| 546 | updateBtnSubmit(att); |
| 547 | F.page.attacher = att /* only for testing via dev console */; |
| 548 | }/* /attachaddV2 */ |
| 549 | |
| 550 | })(window.fossil); |
| 551 |
| --- src/fossil.attach.js | |
| +++ src/fossil.attach.js | |
| @@ -36,10 +36,13 @@ | |
| 36 | |
| 37 | opt.startWith[=0]: if >0 then that many file selection widgets |
| 38 | are automatically activated, as if the user had tapped the Add |
| 39 | button that many times. As a special case, if this is >0 |
| 40 | and the user removes the last entry, a new one is added. |
| 41 | |
| 42 | opt.description[=true]: if true then show the file description |
| 43 | field, otherwise elide it. |
| 44 | |
| 45 | opt.controls = [array of DOM elements]. Optional DOM elements |
| 46 | to inject into the UI element which wraps the "Add" button. |
| 47 | See this.controlsElement. |
| 48 | |
| @@ -70,11 +73,12 @@ | |
| 73 | constructor(opt){ |
| 74 | this.#opt = opt = F.nu({ |
| 75 | addButtonLabel: false, |
| 76 | startWith: 0, |
| 77 | limit: 0, |
| 78 | dryRun: false, |
| 79 | description: true |
| 80 | }, opt); |
| 81 | this.#e.body = D.addClass(D.div(), 'attach-widget'); |
| 82 | const eBtnAdd = this.#e.btnAdd = D.addClass( |
| 83 | D.button(this.#opt.addButtonLabel || 'Add attachment', |
| 84 | ()=>this.#addRow()), |
| @@ -248,15 +252,17 @@ | |
| 252 | "Select/drop file or click the outer border and tap your "+ |
| 253 | "platform's conventional Paste keyboard shortcut." |
| 254 | ); |
| 255 | const eSize = D.addClass(D.span(), 'attach-size'); |
| 256 | eInfo.append(eFilename, eSize); |
| 257 | const eDesc = this.#opt.description |
| 258 | ? D.addClass( |
| 259 | D.attr(D.textarea(), 'placeholder', |
| 260 | 'Optional description...'), |
| 261 | 'hidden', 'attach-desc' |
| 262 | ) |
| 263 | : undefined; |
| 264 | const eRemove = D.addClass( |
| 265 | D.button('Remove', (ev)=>{ |
| 266 | ev.stopPropagation(); |
| 267 | this.#removeRow(rowObj); |
| 268 | }), |
| @@ -311,11 +317,12 @@ | |
| 317 | this.#injestBlob(rowObj, blob); |
| 318 | break; |
| 319 | } |
| 320 | } |
| 321 | }); |
| 322 | eRow.append(eDropzone); |
| 323 | if( eDesc ) eRow.append(eDesc); |
| 324 | rowObj.e = F.nu({ |
| 325 | dropzone: eDropzone, |
| 326 | info: eInfo, |
| 327 | filename: eFilename, |
| 328 | size: eSize, |
| @@ -392,16 +399,22 @@ | |
| 399 | rowObj.file = file; |
| 400 | rowObj.mimeType = file.type || 'application/octet-stream'; |
| 401 | D.clearElement(rowObj.e.filename).append(file.name || 'Pasted Content'); |
| 402 | D.clearElement(rowObj.e.size).append(szLbl, ' ', rowObj.mimeType || ''); |
| 403 | rowObj.e.dropzone.classList.add('populated'); |
| 404 | if( rowObj.e.desc ){ |
| 405 | rowObj.e.desc.classList.remove('hidden'); |
| 406 | } |
| 407 | if( rowObj.e.thumbnail ){ |
| 408 | rowObj.e.thumbnail.remove(); |
| 409 | rowObj.e.thumbnail = undefined; |
| 410 | } |
| 411 | if( file.type?.startsWith?.('image/') || file.type==='BITMAP' ){ |
| 412 | /* Add a thumbnail */ |
| 413 | const img = rowObj.e.thumbnail = D.img(); |
| 414 | rowObj.e.dropzone.insertBefore(img, rowObj.e.remove); |
| 415 | img.classList.add('thumbnail'); |
| 416 | const reader = new FileReader(); |
| 417 | reader.onload = (e)=>img.setAttribute('src', e.target.result); |
| 418 | reader.readAsDataURL(file); |
| 419 | } |
| 420 | if( file.size>F.config.attachmentSizeLimit ){ |
| @@ -469,12 +482,14 @@ | |
| 482 | }/*Attacher*/; |
| 483 | F.Attacher = Attacher; |
| 484 | |
| 485 | const eFormDiv = document.querySelector('#attachadd-form-wrapper'); |
| 486 | if( eFormDiv ){ |
| 487 | /* Inject a file-attachment form. */ |
| 488 | const urlArgs = new URLSearchParams(window.location.search); |
| 489 | let zTarget = urlArgs.get('target'); |
| 490 | let zTo = urlArgs.get('to') || urlArgs.get('from'); |
| 491 | const eBtnSubmit = D.button("Submit"); |
| 492 | eBtnSubmit.type = 'button'; |
| 493 | const updateBtnSubmit = (attacher)=>{ |
| 494 | if( attacher.isPopulated ){ |
| 495 | eBtnSubmit.removeAttribute('disabled'); |
| @@ -488,11 +503,12 @@ | |
| 503 | }; |
| 504 | const att = new Attacher({ |
| 505 | container: eFormDiv, |
| 506 | startWith: 1, |
| 507 | listener: cbAttacherChange, |
| 508 | controls: [eBtnSubmit], |
| 509 | description: false |
| 510 | }); |
| 511 | eBtnSubmit.addEventListener('click', async (ev)=>{ |
| 512 | att.reportError(); |
| 513 | const li = att.collectState(); |
| 514 | if( !li.length ) return; |
| @@ -501,16 +517,18 @@ | |
| 517 | D.disable(eBtnSubmit); |
| 518 | const fd = new FormData(); |
| 519 | let i = 0; |
| 520 | for(const row of li){ |
| 521 | ++i; |
| 522 | fd.append('file'+i, row.content); |
| 523 | if( row.description ) fd.append('file'+i+'_desc', row.description); |
| 524 | } |
| 525 | for( const eIn of eFormDiv.querySelectorAll(':scope > input[type="hidden"]') ){ |
| 526 | if( eIn.name==='target' ){ |
| 527 | zTarget = eIn.value; |
| 528 | }else if( eIn.name==='to' || (eIn.name==='from' && !zTo) ){ |
| 529 | zTo = eIn.value; |
| 530 | } |
| 531 | fd.append(eIn.name, eIn.value) |
| 532 | } |
| 533 | if( att.isDryRun ){ |
| 534 | fd.append('dryrun', '1'); |
| @@ -528,23 +546,21 @@ | |
| 546 | if( jr?.error || !resp.ok ){ |
| 547 | const msg = err ? err.message : (jr?.error || resp.statusText); |
| 548 | att.reportError("Attaching failed: ", msg); |
| 549 | }else{ |
| 550 | att.clear(); |
| 551 | let to = zTo || jr?.redirect; |
| 552 | if( to ){ |
| 553 | if( '/'!==to[0] ){ |
| 554 | to = F.repoUrl(to); |
| 555 | } |
| 556 | window.location = to; |
| 557 | }else{ |
| 558 | window.location = '?target='+zTarget+'&'+Date.now(); |
| 559 | } |
| 560 | } |
| 561 | })/*submit handler*/; |
| 562 | updateBtnSubmit(att); |
| 563 | F.page.attacher = att /* only for testing via dev console */; |
| 564 | }/* /attachaddV2 */ |
| 565 | |
| 566 | })(window.fossil); |
| 567 |