Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | The /subscribe page now creates entries in the subscriber table and sends verification emails. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | email-alerts |
Files: | files | file ages | folders |
SHA3-256: |
31be2e17a4833ce839712cf7757687e0 |
User & Date: | drh 2018-06-21 19:10:00.187 |
Context
2018-06-21
| ||
19:51 | Further progress toward get email subscription webpages working. ... (check-in: adf068fa user: drh tags: email-alerts) | |
19:10 | The /subscribe page now creates entries in the subscriber table and sends verification emails. ... (check-in: 31be2e17 user: drh tags: email-alerts) | |
17:07 | Merge codecheck1 enhancements from trunk. ... (check-in: 4d13d948 user: drh tags: email-alerts) | |
Changes
Changes to src/attach.c.
︙ | ︙ | |||
375 376 377 378 379 380 381 | zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", zTkt, zTkt); } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); } | | | 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", zTkt, zTkt); } if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); if( P("cancel") ){ cgi_redirect(zFrom); } if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){ int needModerator = (zTkt!=0 && ticket_need_moderation(0)) || (zPage!=0 && wiki_need_moderation(0)); const char *zComment = PD("comment", ""); attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment); cgi_redirect(zFrom); } style_header("Add Attachment"); |
︙ | ︙ |
Changes to src/captcha.c.
︙ | ︙ | |||
495 496 497 498 499 500 501 | ** ** If no captcha is required or if the correct captcha is supplied, return ** true (non-zero). ** ** The query parameters examined are "captchaseed" for the seed value and ** "captcha" for text that the user types in response to the captcha prompt. */ | | | | 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | ** ** If no captcha is required or if the correct captcha is supplied, return ** true (non-zero). ** ** The query parameters examined are "captchaseed" for the seed value and ** "captcha" for text that the user types in response to the captcha prompt. */ int captcha_is_correct(int bAlwaysNeeded){ const char *zSeed; const char *zEntered; const char *zDecode; char z[30]; int i; if( !bAlwaysNeeded && !captcha_needed() ){ return 1; /* No captcha needed */ } zSeed = P("captchaseed"); if( zSeed==0 ) return 0; zEntered = P("captcha"); if( zEntered==0 || strlen(zEntered)!=8 ) return 0; zDecode = captcha_decode((unsigned int)atoi(zSeed)); |
︙ | ︙ | |||
591 592 593 594 595 596 597 | return 0; } } #endif zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x")); zCookieValue = P(zCookieName); if( zCookieValue && atoi(zCookieValue)==1 ) return 0; | | | 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | return 0; } } #endif zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x")); zCookieValue = P(zCookieName); if( zCookieValue && atoi(zCookieValue)==1 ) return 0; if( captcha_is_correct(0) ){ cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600); return 0; } /* This appears to be a spider. Offer the captcha */ style_header("Verification"); @ <form method='POST' action='%s(g.zPath)'> |
︙ | ︙ |
Changes to src/cgi.c.
︙ | ︙ | |||
54 55 56 57 58 59 60 | ** does the same except "y" is returned in place of NULL if there is not match. */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) | | | | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | ** does the same except "y" is returned in place of NULL if there is not match. */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 |
︙ | ︙ | |||
1120 1121 1122 1123 1124 1125 1126 | int cgi_parameter_boolean(const char *zName){ const char *zIn = cgi_parameter(zName, 0); if( zIn==0 ) return 0; return zIn[0]==0 || is_truth(zIn); } /* | > | > > > > > > | | > | > > | > > > > | 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 | int cgi_parameter_boolean(const char *zName){ const char *zIn = cgi_parameter(zName, 0); if( zIn==0 ) return 0; return zIn[0]==0 || is_truth(zIn); } /* ** Return either an empty string "" or the string "checked" depending ** on whether or not parameter zName has value iValue. If parameter ** zName does not exist, that is assumed to be the same as value 0. ** ** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y) ** macro generateds " checked" if the value of parameter x equals integer y. ** PCK(x) is the same as PIF(x,1). These macros are used to generate ** the "checked" attribute on checkbox and radio controls of forms. */ const char *cgi_parameter_checked(const char *zName, int iValue){ const char *zIn = cgi_parameter(zName,0); int x; if( zIn==0 ){ x = 0; }else if( !fossil_isdigit(zIn[0]) ){ x = is_truth(zIn); }else{ x = atoi(zIn); } return x==iValue ? "checked" : ""; } /* ** Return the name of the i-th CGI parameter. Return NULL if there ** are fewer than i registered CGI parameters. */ const char *cgi_parameter_name(int i){ |
︙ | ︙ |
Changes to src/codecheck1.c.
︙ | ︙ | |||
329 330 331 332 333 334 335 | /* ** Return true if the input is an argument that is never safe for use ** with %s. */ static int never_safe(const char *z){ if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0; | | > > > > | 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | /* ** Return true if the input is an argument that is never safe for use ** with %s. */ static int never_safe(const char *z){ if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0; if( z[0]=='P' ){ if( strncmp(z,"PIF(",4)==0 ) return 0; if( strncmp(z,"PCK(",4)==0 ) return 0; return 1; } if( strncmp(z,"cgi_param",9)==0 ) return 1; return 0; } /* ** Processing flags */ |
︙ | ︙ |
Changes to src/email.c.
︙ | ︙ | |||
46 47 48 49 50 51 52 | @ -- Probably different codes will be added in the future. In the future @ -- we might also add a separate table that allows subscribing to email @ -- notifications for specific branches or tags or tickets. @ -- @ CREATE TABLE repository.subscriber( @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use @ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use | | | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | @ -- Probably different codes will be added in the future. In the future @ -- we might also add a separate table that allows subscribing to email @ -- notifications for specific branches or tags or tickets. @ -- @ CREATE TABLE repository.subscriber( @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use @ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use @ semail TEXT UNIQUE COLLATE nocase,-- email address @ suname TEXT, -- corresponding USER entry @ sverify BOOLEAN, -- email address verified @ sdonotcall BOOLEAN, -- true for Do Not Call @ sdigest BOOLEAN, -- true for daily digests only @ ssub TEXT, -- baseline subscriptions @ sctime DATE, -- When this entry was created. JulianDay @ smtime DATE, -- Last change. JulianDay |
︙ | ︙ | |||
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | print_setting(pSetting); } } else{ usage("reset|send|setting"); } } /* ** WEBPAGE: subscribe ** ** Allow users to subscribe to email notifications, or to change or ** verify their subscription. */ void subscribe_page(void){ int needCaptcha; unsigned int uSeed; const char *zDecoded; char *zCaptcha; login_check_credentials(); if( !g.perm.EmailAlert ){ login_needed(g.anon.EmailAlert); return; } if( login_is_individual() && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin) ){ cgi_redirect("%R/alerts"); return; } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < > > > > > > > > > > > | > < > > > > | > | | | | | | > > > > > > > | > | 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 | print_setting(pSetting); } } else{ usage("reset|send|setting"); } } /* ** Do error checking on a submitted subscription form. Return TRUE ** if the submission is valid. Return false if any problems are seen. */ static int subscribe_error_check( int *peErr, /* Type of error */ char **pzErr, /* Error message text */ int needCaptcha /* True if captcha check needed */ ){ const char *zEAddr; int i, j, n; char c; *peErr = 0; *pzErr = 0; /* Check the validity of the email address. ** ** (1) Exactly one '@' character. ** (2) No other characters besides [a-zA-Z0-9._-] */ zEAddr = P("e"); if( zEAddr==0 ) return 0; for(i=j=0; (c = zEAddr[i])!=0; i++){ if( c=='@' ){ n = i; j++; continue; } if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' ){ *peErr = 1; *pzErr = mprintf("illegal character in email address: 0x%x '%c'", c, c); return 0; } } if( j!=1 ){ *peErr = 1; *pzErr = mprintf("email address should contain exactly one '@'"); return 0; } if( n<1 ){ *peErr = 1; *pzErr = mprintf("name missing before '@' in email address"); return 0; } if( n>i-5 ){ *peErr = 1; *pzErr = mprintf("email domain too short"); return 0; } /* Verify the captcha */ if( needCaptcha && !captcha_is_correct(1) ){ *peErr = 2; *pzErr = mprintf("incorrect security code"); return 0; } /* Check to make sure the email address is available for reuse */ if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){ *peErr = 1; *pzErr = mprintf("this email address is used by someone else"); return 0; } /* If we reach this point, all is well */ return 1; } /* ** Text of email message sent in order to confirm a subscription. */ static const char zConfirmMsg[] = @ Someone has signed you up for email alerts on the Fossil repository @ at %R. @ @ To confirm your subscription and begin receiving alerts, click on @ the following hyperlink: @ @ %R/alerts/%s @ @ Save the hyperlink above! You can reuse this same hyperlink to @ unsubscribe or to change the kinds of alerts you receive. @ @ If you do not want to subscribe, you can simply ignore this message. @ You will not be contacted again. @ ; /* ** WEBPAGE: subscribe ** ** Allow users to subscribe to email notifications, or to change or ** verify their subscription. */ void subscribe_page(void){ int needCaptcha; unsigned int uSeed; const char *zDecoded; char *zCaptcha; char *zErr = 0; int eErr = 0; login_check_credentials(); if( !g.perm.EmailAlert ){ login_needed(g.anon.EmailAlert); return; } if( login_is_individual() && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin) ){ /* This person is already signed up for email alerts. Jump ** to the screen that lets them edit their alert preferences. */ cgi_redirect("%R/alerts"); return; } needCaptcha = !login_is_individual(); if( P("submit") && cgi_csrf_safe(1) && subscribe_error_check(&eErr,&zErr,needCaptcha) ){ /* A validated request for a new subscription has been received. */ char ssub[20]; const char *zEAddr = P("e"); sqlite3_int64 id; /* New subscriber Id */ const char *zCode; /* New subscriber code (in hex) */ int nsub = 0; if( PB("sa") ) ssub[nsub++] = 'a'; if( PB("sc") ) ssub[nsub++] = 'c'; if( PB("st") ) ssub[nsub++] = 't'; if( PB("sw") ) ssub[nsub++] = 'w'; ssub[nsub] = 0; db_multi_exec( "INSERT INTO subscriber(subscriberCode,semail,suname," " sverify,sdonotcall,sdigest,ssub,sctime,smtime,smip)" "VALUES(randomblob(32),%Q,%Q,%d,0,%d,%Q," " julianday('now'),julianday('now'),%Q)", /* semail */ zEAddr, /* suname */ needCaptcha==0 ? g.zLogin : 0, /* sverify */ needCaptcha==0, /* sdigest */ PB("di"), /* ssub */ ssub, /* smip */ g.zIpAddr ); id = db_last_insert_rowid(); zCode = db_text(0, "SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld", id); if( !needCaptcha ){ /* The new subscription has been added on behalf of a logged-in user. ** No verification is required. Jump immediately to /alerts page. */ cgi_redirectf("%R/alerts/%s", zCode); return; }else{ /* We need to send a verification email */ Blob hdr, body; blob_init(&hdr,0,0); blob_init(&body,0,0); blob_appendf(&hdr, "To: %s\n", zEAddr); blob_appendf(&hdr, "Subject: Subscription verification\n"); blob_appendf(&body, zConfirmMsg/*works-like:"%s"*/, zCode); email_send(&hdr, &body, 0, 0); style_header("Email Alert Verification"); @ <p>An email has been sent to "%h(zEAddr)". That email contains a @ hyperlink that you must click on in order to activate your @ subscription.</p> style_footer(); } return; } style_header("Signup For Email Alerts"); @ <p>To receive email notifications for changes to this @ repository, fill out the form below and press "Submit" button.</p> form_begin(0, "%R/subscribe"); @ <table class="subscribe"> @ <tr> @ <td class="form_label">Email Address:</td> @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td> if( eErr==1 ){ @ <td><span class="loginError">← %h(zErr)</span></td> } @ </tr> if( needCaptcha ){ uSeed = captcha_seed(); zDecoded = captcha_decode(uSeed); zCaptcha = captcha_render(zDecoded); @ <tr> @ <td class="form_label">Security Code:</td> @ <td><input type="text" name="captcha" value="" size="30"> @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> if( eErr==2 ){ @ <td><span class="loginError">← %h(zErr)</span></td> } @ </tr> } if( g.perm.Admin ){ @ <tr> @ <td class="form_label">User:</td> @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \ @ size="30"></td> if( eErr==3 ){ @ <td><span class="loginError">← %h(zErr)</span></td> } @ </tr> } @ <tr> @ <td class="form_label">Options:</td> @ <td><label><input type="checkbox" name="sa" %s(PCK("sa"))>\ @ Announcements</label><br> @ <label><input type="checkbox" name="sc" %s(PCK("sc"))>\ @ Check-ins</label><br> @ <label><input type="checkbox" name="st" %s(PCK("st"))>\ @ Ticket changes</label><br> @ <label><input type="checkbox" name="sw" %s(PCK("sw"))>\ @ Wiki</label><br> @ <label><input type="checkbox" name="di" %s(PCK("di"))>\ @ Daily digest only</label><br> if( g.perm.Admin ){ @ <label><input type="checkbox" name="vi" %s(PCK("vi"))>\ @ Verified</label><br> @ <label><input type="checkbox" name="dnc" %s(PCK("dnc"))>\ @ Do not call</label><br> } @ </td> @ </tr> @ <tr> @ <td></td> @ <td><input type="submit" name="submit" value="Submit"></td> @ </tr> @ </table> if( needCaptcha ){ @ <div class="captcha"><table class="captcha"><tr><td><pre> @ %h(zCaptcha) @ </pre> @ Enter the 8 characters above in the "Security Code" box @ </td></tr></table></div> } @ </form> fossil_free(zErr); style_footer(); } /* ** WEBPAGE: alerts ** ** Edit email alert and notification settings. |
︙ | ︙ |
Changes to src/tkt.c.
︙ | ︙ | |||
592 593 594 595 596 597 598 | const char *zUuid; int i; int nJ = 0; Blob tktchng, cksum; int needMod; login_verify_csrf_secret(); | | | 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 | const char *zUuid; int i; int nJ = 0; Blob tktchng, cksum; int needMod; login_verify_csrf_secret(); if( !captcha_is_correct(0) ){ @ <p class="generalError">Error: Incorrect security code.</p> return TH_OK; } zUuid = (const char *)pUuid; blob_zero(&tktchng); zDate = date_in_standard_format("now"); blob_appendf(&tktchng, "D %s\n", zDate); |
︙ | ︙ |
Changes to src/wiki.c.
︙ | ︙ | |||
532 533 534 535 536 537 538 | } if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ zBody = pWiki->zWiki; zMimetype = pWiki->zMimetype; } } if( P("submit")!=0 && zBody!=0 | | | 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 | } if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ zBody = pWiki->zWiki; zMimetype = pWiki->zMimetype; } } if( P("submit")!=0 && zBody!=0 && (goodCaptcha = captcha_is_correct(0)) ){ char *zDate; Blob cksum; blob_zero(&wiki); db_begin_transaction(); if( isSandbox ){ db_set("sandbox",zBody,0); |
︙ | ︙ | |||
756 757 758 759 760 761 762 | } } if( !g.perm.ApndWiki ){ login_needed(g.anon.ApndWiki); return; } if( P("submit")!=0 && P("r")!=0 && P("u")!=0 | | | 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 | } } if( !g.perm.ApndWiki ){ login_needed(g.anon.ApndWiki); return; } if( P("submit")!=0 && P("r")!=0 && P("u")!=0 && (goodCaptcha = captcha_is_correct(0)) ){ char *zDate; Blob cksum; Blob body; Blob wiki; Manifest *pWiki = 0; |
︙ | ︙ |