Fossil SCM

The /subscribe page now creates entries in the subscriber table and sends verification emails.

drh 2018-06-21 19:10 UTC email-alerts
Commit 31be2e17a4833ce839712cf7757687e035d8cfb6fea16df19c4d4da9e2e9d2b0
+1 -1
--- src/attach.c
+++ src/attach.c
@@ -377,11 +377,11 @@
377377
}
378378
if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
379379
if( P("cancel") ){
380380
cgi_redirect(zFrom);
381381
}
382
- if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
382
+ if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
383383
int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
384384
(zPage!=0 && wiki_need_moderation(0));
385385
const char *zComment = PD("comment", "");
386386
attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
387387
cgi_redirect(zFrom);
388388
--- src/attach.c
+++ src/attach.c
@@ -377,11 +377,11 @@
377 }
378 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
379 if( P("cancel") ){
380 cgi_redirect(zFrom);
381 }
382 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
383 int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
384 (zPage!=0 && wiki_need_moderation(0));
385 const char *zComment = PD("comment", "");
386 attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
387 cgi_redirect(zFrom);
388
--- src/attach.c
+++ src/attach.c
@@ -377,11 +377,11 @@
377 }
378 if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
379 if( P("cancel") ){
380 cgi_redirect(zFrom);
381 }
382 if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){
383 int needModerator = (zTkt!=0 && ticket_need_moderation(0)) ||
384 (zPage!=0 && wiki_need_moderation(0));
385 const char *zComment = PD("comment", "");
386 attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment);
387 cgi_redirect(zFrom);
388
+3 -3
--- src/captcha.c
+++ src/captcha.c
@@ -497,17 +497,17 @@
497497
** true (non-zero).
498498
**
499499
** The query parameters examined are "captchaseed" for the seed value and
500500
** "captcha" for text that the user types in response to the captcha prompt.
501501
*/
502
-int captcha_is_correct(void){
502
+int captcha_is_correct(int bAlwaysNeeded){
503503
const char *zSeed;
504504
const char *zEntered;
505505
const char *zDecode;
506506
char z[30];
507507
int i;
508
- if( !captcha_needed() ){
508
+ if( !bAlwaysNeeded && !captcha_needed() ){
509509
return 1; /* No captcha needed */
510510
}
511511
zSeed = P("captchaseed");
512512
if( zSeed==0 ) return 0;
513513
zEntered = P("captcha");
@@ -593,11 +593,11 @@
593593
}
594594
#endif
595595
zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x"));
596596
zCookieValue = P(zCookieName);
597597
if( zCookieValue && atoi(zCookieValue)==1 ) return 0;
598
- if( captcha_is_correct() ){
598
+ if( captcha_is_correct(0) ){
599599
cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600);
600600
return 0;
601601
}
602602
603603
/* This appears to be a spider. Offer the captcha */
604604
--- src/captcha.c
+++ src/captcha.c
@@ -497,17 +497,17 @@
497 ** true (non-zero).
498 **
499 ** The query parameters examined are "captchaseed" for the seed value and
500 ** "captcha" for text that the user types in response to the captcha prompt.
501 */
502 int captcha_is_correct(void){
503 const char *zSeed;
504 const char *zEntered;
505 const char *zDecode;
506 char z[30];
507 int i;
508 if( !captcha_needed() ){
509 return 1; /* No captcha needed */
510 }
511 zSeed = P("captchaseed");
512 if( zSeed==0 ) return 0;
513 zEntered = P("captcha");
@@ -593,11 +593,11 @@
593 }
594 #endif
595 zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x"));
596 zCookieValue = P(zCookieName);
597 if( zCookieValue && atoi(zCookieValue)==1 ) return 0;
598 if( captcha_is_correct() ){
599 cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600);
600 return 0;
601 }
602
603 /* This appears to be a spider. Offer the captcha */
604
--- src/captcha.c
+++ src/captcha.c
@@ -497,17 +497,17 @@
497 ** true (non-zero).
498 **
499 ** The query parameters examined are "captchaseed" for the seed value and
500 ** "captcha" for text that the user types in response to the captcha prompt.
501 */
502 int captcha_is_correct(int bAlwaysNeeded){
503 const char *zSeed;
504 const char *zEntered;
505 const char *zDecode;
506 char z[30];
507 int i;
508 if( !bAlwaysNeeded && !captcha_needed() ){
509 return 1; /* No captcha needed */
510 }
511 zSeed = P("captchaseed");
512 if( zSeed==0 ) return 0;
513 zEntered = P("captcha");
@@ -593,11 +593,11 @@
593 }
594 #endif
595 zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x"));
596 zCookieValue = P(zCookieName);
597 if( zCookieValue && atoi(zCookieValue)==1 ) return 0;
598 if( captcha_is_correct(0) ){
599 cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600);
600 return 0;
601 }
602
603 /* This appears to be a spider. Offer the captcha */
604
+21 -7
--- src/cgi.c
+++ src/cgi.c
@@ -56,12 +56,12 @@
5656
#define P(x) cgi_parameter((x),0)
5757
#define PD(x,y) cgi_parameter((x),(y))
5858
#define PT(x) cgi_parameter_trimmed((x),0)
5959
#define PDT(x,y) cgi_parameter_trimmed((x),(y))
6060
#define PB(x) cgi_parameter_boolean(x)
61
-#define P10(x) cgi_parameter_01(x,1)
62
-#define P01(x) cgi_parameter_01(x,0)
61
+#define PCK(x) cgi_parameter_checked(x,1)
62
+#define PIF(x,y) cgi_parameter_checked(x,y)
6363
6464
6565
/*
6666
** Destinations for output text.
6767
*/
@@ -1122,16 +1122,30 @@
11221122
if( zIn==0 ) return 0;
11231123
return zIn[0]==0 || is_truth(zIn);
11241124
}
11251125
11261126
/*
1127
-** Return 0 or 1 according to the value of CGI parameter zName.
1127
+** Return either an empty string "" or the string "checked" depending
1128
+** on whether or not parameter zName has value iValue. If parameter
1129
+** zName does not exist, that is assumed to be the same as value 0.
1130
+**
1131
+** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y)
1132
+** macro generateds " checked" if the value of parameter x equals integer y.
1133
+** PCK(x) is the same as PIF(x,1). These macros are used to generate
1134
+** the "checked" attribute on checkbox and radio controls of forms.
11281135
*/
1129
-int cgi_parameter_01(const char *zName, int iDefault){
1130
- const char *zIn = cgi_parameter(zName, 0);
1131
- if( zIn==0 ) return iDefault;
1132
- return zIn[0]==0 || is_truth(zIn);
1136
+const char *cgi_parameter_checked(const char *zName, int iValue){
1137
+ const char *zIn = cgi_parameter(zName,0);
1138
+ int x;
1139
+ if( zIn==0 ){
1140
+ x = 0;
1141
+ }else if( !fossil_isdigit(zIn[0]) ){
1142
+ x = is_truth(zIn);
1143
+ }else{
1144
+ x = atoi(zIn);
1145
+ }
1146
+ return x==iValue ? "checked" : "";
11331147
}
11341148
11351149
/*
11361150
** Return the name of the i-th CGI parameter. Return NULL if there
11371151
** are fewer than i registered CGI parameters.
11381152
--- src/cgi.c
+++ src/cgi.c
@@ -56,12 +56,12 @@
56 #define P(x) cgi_parameter((x),0)
57 #define PD(x,y) cgi_parameter((x),(y))
58 #define PT(x) cgi_parameter_trimmed((x),0)
59 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
60 #define PB(x) cgi_parameter_boolean(x)
61 #define P10(x) cgi_parameter_01(x,1)
62 #define P01(x) cgi_parameter_01(x,0)
63
64
65 /*
66 ** Destinations for output text.
67 */
@@ -1122,16 +1122,30 @@
1122 if( zIn==0 ) return 0;
1123 return zIn[0]==0 || is_truth(zIn);
1124 }
1125
1126 /*
1127 ** Return 0 or 1 according to the value of CGI parameter zName.
 
 
 
 
 
 
 
1128 */
1129 int cgi_parameter_01(const char *zName, int iDefault){
1130 const char *zIn = cgi_parameter(zName, 0);
1131 if( zIn==0 ) return iDefault;
1132 return zIn[0]==0 || is_truth(zIn);
 
 
 
 
 
 
 
1133 }
1134
1135 /*
1136 ** Return the name of the i-th CGI parameter. Return NULL if there
1137 ** are fewer than i registered CGI parameters.
1138
--- src/cgi.c
+++ src/cgi.c
@@ -56,12 +56,12 @@
56 #define P(x) cgi_parameter((x),0)
57 #define PD(x,y) cgi_parameter((x),(y))
58 #define PT(x) cgi_parameter_trimmed((x),0)
59 #define PDT(x,y) cgi_parameter_trimmed((x),(y))
60 #define PB(x) cgi_parameter_boolean(x)
61 #define PCK(x) cgi_parameter_checked(x,1)
62 #define PIF(x,y) cgi_parameter_checked(x,y)
63
64
65 /*
66 ** Destinations for output text.
67 */
@@ -1122,16 +1122,30 @@
1122 if( zIn==0 ) return 0;
1123 return zIn[0]==0 || is_truth(zIn);
1124 }
1125
1126 /*
1127 ** Return either an empty string "" or the string "checked" depending
1128 ** on whether or not parameter zName has value iValue. If parameter
1129 ** zName does not exist, that is assumed to be the same as value 0.
1130 **
1131 ** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y)
1132 ** macro generateds " checked" if the value of parameter x equals integer y.
1133 ** PCK(x) is the same as PIF(x,1). These macros are used to generate
1134 ** the "checked" attribute on checkbox and radio controls of forms.
1135 */
1136 const char *cgi_parameter_checked(const char *zName, int iValue){
1137 const char *zIn = cgi_parameter(zName,0);
1138 int x;
1139 if( zIn==0 ){
1140 x = 0;
1141 }else if( !fossil_isdigit(zIn[0]) ){
1142 x = is_truth(zIn);
1143 }else{
1144 x = atoi(zIn);
1145 }
1146 return x==iValue ? "checked" : "";
1147 }
1148
1149 /*
1150 ** Return the name of the i-th CGI parameter. Return NULL if there
1151 ** are fewer than i registered CGI parameters.
1152
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -331,11 +331,15 @@
331331
** Return true if the input is an argument that is never safe for use
332332
** with %s.
333333
*/
334334
static int never_safe(const char *z){
335335
if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0;
336
- if( z[0]=='P' ) return 1; /* CGI macros like P() and PD() */
336
+ if( z[0]=='P' ){
337
+ if( strncmp(z,"PIF(",4)==0 ) return 0;
338
+ if( strncmp(z,"PCK(",4)==0 ) return 0;
339
+ return 1;
340
+ }
337341
if( strncmp(z,"cgi_param",9)==0 ) return 1;
338342
return 0;
339343
}
340344
341345
/*
342346
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -331,11 +331,15 @@
331 ** Return true if the input is an argument that is never safe for use
332 ** with %s.
333 */
334 static int never_safe(const char *z){
335 if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0;
336 if( z[0]=='P' ) return 1; /* CGI macros like P() and PD() */
 
 
 
 
337 if( strncmp(z,"cgi_param",9)==0 ) return 1;
338 return 0;
339 }
340
341 /*
342
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -331,11 +331,15 @@
331 ** Return true if the input is an argument that is never safe for use
332 ** with %s.
333 */
334 static int never_safe(const char *z){
335 if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0;
336 if( z[0]=='P' ){
337 if( strncmp(z,"PIF(",4)==0 ) return 0;
338 if( strncmp(z,"PCK(",4)==0 ) return 0;
339 return 1;
340 }
341 if( strncmp(z,"cgi_param",9)==0 ) return 1;
342 return 0;
343 }
344
345 /*
346
+178 -13
--- src/email.c
+++ src/email.c
@@ -48,11 +48,11 @@
4848
@ -- notifications for specific branches or tags or tickets.
4949
@ --
5050
@ CREATE TABLE repository.subscriber(
5151
@ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use
5252
@ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use
53
-@ semail TEXT, -- email address
53
+@ semail TEXT UNIQUE COLLATE nocase,-- email address
5454
@ suname TEXT, -- corresponding USER entry
5555
@ sverify BOOLEAN, -- email address verified
5656
@ sdonotcall BOOLEAN, -- true for Do Not Call
5757
@ sdigest BOOLEAN, -- true for daily digests only
5858
@ ssub TEXT, -- baseline subscriptions
@@ -449,10 +449,100 @@
449449
}
450450
else{
451451
usage("reset|send|setting");
452452
}
453453
}
454
+
455
+/*
456
+** Do error checking on a submitted subscription form. Return TRUE
457
+** if the submission is valid. Return false if any problems are seen.
458
+*/
459
+static int subscribe_error_check(
460
+ int *peErr, /* Type of error */
461
+ char **pzErr, /* Error message text */
462
+ int needCaptcha /* True if captcha check needed */
463
+){
464
+ const char *zEAddr;
465
+ int i, j, n;
466
+ char c;
467
+
468
+ *peErr = 0;
469
+ *pzErr = 0;
470
+
471
+ /* Check the validity of the email address.
472
+ **
473
+ ** (1) Exactly one '@' character.
474
+ ** (2) No other characters besides [a-zA-Z0-9._-]
475
+ */
476
+ zEAddr = P("e");
477
+ if( zEAddr==0 ) return 0;
478
+ for(i=j=0; (c = zEAddr[i])!=0; i++){
479
+ if( c=='@' ){
480
+ n = i;
481
+ j++;
482
+ continue;
483
+ }
484
+ if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' ){
485
+ *peErr = 1;
486
+ *pzErr = mprintf("illegal character in email address: 0x%x '%c'",
487
+ c, c);
488
+ return 0;
489
+ }
490
+ }
491
+ if( j!=1 ){
492
+ *peErr = 1;
493
+ *pzErr = mprintf("email address should contain exactly one '@'");
494
+ return 0;
495
+ }
496
+ if( n<1 ){
497
+ *peErr = 1;
498
+ *pzErr = mprintf("name missing before '@' in email address");
499
+ return 0;
500
+ }
501
+ if( n>i-5 ){
502
+ *peErr = 1;
503
+ *pzErr = mprintf("email domain too short");
504
+ return 0;
505
+ }
506
+
507
+ /* Verify the captcha */
508
+ if( needCaptcha && !captcha_is_correct(1) ){
509
+ *peErr = 2;
510
+ *pzErr = mprintf("incorrect security code");
511
+ return 0;
512
+ }
513
+
514
+ /* Check to make sure the email address is available for reuse */
515
+ if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
516
+ *peErr = 1;
517
+ *pzErr = mprintf("this email address is used by someone else");
518
+ return 0;
519
+ }
520
+
521
+ /* If we reach this point, all is well */
522
+ return 1;
523
+}
524
+
525
+/*
526
+** Text of email message sent in order to confirm a subscription.
527
+*/
528
+static const char zConfirmMsg[] =
529
+@ Someone has signed you up for email alerts on the Fossil repository
530
+@ at %R.
531
+@
532
+@ To confirm your subscription and begin receiving alerts, click on
533
+@ the following hyperlink:
534
+@
535
+@ %R/alerts/%s
536
+@
537
+@ Save the hyperlink above! You can reuse this same hyperlink to
538
+@ unsubscribe or to change the kinds of alerts you receive.
539
+@
540
+@ If you do not want to subscribe, you can simply ignore this message.
541
+@ You will not be contacted again.
542
+@
543
+;
454544
455545
/*
456546
** WEBPAGE: subscribe
457547
**
458548
** Allow users to subscribe to email notifications, or to change or
@@ -461,66 +551,140 @@
461551
void subscribe_page(void){
462552
int needCaptcha;
463553
unsigned int uSeed;
464554
const char *zDecoded;
465555
char *zCaptcha;
556
+ char *zErr = 0;
557
+ int eErr = 0;
466558
467559
login_check_credentials();
468560
if( !g.perm.EmailAlert ){
469561
login_needed(g.anon.EmailAlert);
470562
return;
471563
}
472564
if( login_is_individual()
473565
&& db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
474566
){
567
+ /* This person is already signed up for email alerts. Jump
568
+ ** to the screen that lets them edit their alert preferences.
569
+ */
475570
cgi_redirect("%R/alerts");
476571
return;
477572
}
478
- style_header("Email Subscription");
479
- needCaptcha = P("usecaptcha")!=0 || !login_is_individual();
573
+ needCaptcha = !login_is_individual();
574
+ if( P("submit")
575
+ && cgi_csrf_safe(1)
576
+ && subscribe_error_check(&eErr,&zErr,needCaptcha)
577
+ ){
578
+ /* A validated request for a new subscription has been received. */
579
+ char ssub[20];
580
+ const char *zEAddr = P("e");
581
+ sqlite3_int64 id; /* New subscriber Id */
582
+ const char *zCode; /* New subscriber code (in hex) */
583
+ int nsub = 0;
584
+ if( PB("sa") ) ssub[nsub++] = 'a';
585
+ if( PB("sc") ) ssub[nsub++] = 'c';
586
+ if( PB("st") ) ssub[nsub++] = 't';
587
+ if( PB("sw") ) ssub[nsub++] = 'w';
588
+ ssub[nsub] = 0;
589
+ db_multi_exec(
590
+ "INSERT INTO subscriber(subscriberCode,semail,suname,"
591
+ " sverify,sdonotcall,sdigest,ssub,sctime,smtime,smip)"
592
+ "VALUES(randomblob(32),%Q,%Q,%d,0,%d,%Q,"
593
+ " julianday('now'),julianday('now'),%Q)",
594
+ /* semail */ zEAddr,
595
+ /* suname */ needCaptcha==0 ? g.zLogin : 0,
596
+ /* sverify */ needCaptcha==0,
597
+ /* sdigest */ PB("di"),
598
+ /* ssub */ ssub,
599
+ /* smip */ g.zIpAddr
600
+ );
601
+ id = db_last_insert_rowid();
602
+ zCode = db_text(0,
603
+ "SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld",
604
+ id);
605
+ if( !needCaptcha ){
606
+ /* The new subscription has been added on behalf of a logged-in user.
607
+ ** No verification is required. Jump immediately to /alerts page.
608
+ */
609
+ cgi_redirectf("%R/alerts/%s", zCode);
610
+ return;
611
+ }else{
612
+ /* We need to send a verification email */
613
+ Blob hdr, body;
614
+ blob_init(&hdr,0,0);
615
+ blob_init(&body,0,0);
616
+ blob_appendf(&hdr, "To: %s\n", zEAddr);
617
+ blob_appendf(&hdr, "Subject: Subscription verification\n");
618
+ blob_appendf(&body, zConfirmMsg/*works-like:"%s"*/, zCode);
619
+ email_send(&hdr, &body, 0, 0);
620
+ style_header("Email Alert Verification");
621
+ @ <p>An email has been sent to "%h(zEAddr)". That email contains a
622
+ @ hyperlink that you must click on in order to activate your
623
+ @ subscription.</p>
624
+ style_footer();
625
+ }
626
+ return;
627
+ }
628
+ style_header("Signup For Email Alerts");
629
+ @ <p>To receive email notifications for changes to this
630
+ @ repository, fill out the form below and press "Submit" button.</p>
480631
form_begin(0, "%R/subscribe");
481632
@ <table class="subscribe">
482633
@ <tr>
483634
@ <td class="form_label">Email&nbsp;Address:</td>
484635
@ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
485
- @ <td></td>
636
+ if( eErr==1 ){
637
+ @ <td><span class="loginError">&larr; %h(zErr)</span></td>
638
+ }
486639
@ </tr>
487640
if( needCaptcha ){
488641
uSeed = captcha_seed();
489642
zDecoded = captcha_decode(uSeed);
490643
zCaptcha = captcha_render(zDecoded);
491644
@ <tr>
492645
@ <td class="form_label">Security Code:</td>
493646
@ <td><input type="text" name="captcha" value="" size="30">
494
- @ <input type="hidden" name="usecaptcha" value="1"></td>
495647
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
648
+ if( eErr==2 ){
649
+ @ <td><span class="loginError">&larr; %h(zErr)</span></td>
650
+ }
496651
@ </tr>
497652
}
498653
if( g.perm.Admin ){
499654
@ <tr>
500655
@ <td class="form_label">User:</td>
501656
@ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
502657
@ size="30"></td>
503
- @ <td></td>
658
+ if( eErr==3 ){
659
+ @ <td><span class="loginError">&larr; %h(zErr)</span></td>
660
+ }
504661
@ </tr>
505662
}
506663
@ <tr>
507664
@ <td class="form_label">Options:</td>
508
- @ <td><label><input type="checkbox" name="sa" value="%d(P10("sa"))">\
665
+ @ <td><label><input type="checkbox" name="sa" %s(PCK("sa"))>\
509666
@ Announcements</label><br>
510
- @ <label><input type="checkbox" name="sc" value="%d(P01("sc"))">\
667
+ @ <label><input type="checkbox" name="sc" %s(PCK("sc"))>\
511668
@ Check-ins</label><br>
512
- @ <label><input type="checkbox" name="st" value="%d(P01("st"))">\
669
+ @ <label><input type="checkbox" name="st" %s(PCK("st"))>\
513670
@ Ticket changes</label><br>
514
- @ <label><input type="checkbox" name="sw" value="%d(P01("sw"))">\
671
+ @ <label><input type="checkbox" name="sw" %s(PCK("sw"))>\
515672
@ Wiki</label><br>
516
- @ <label><input type="checkbox" name="di" value="%d(P01("di"))">\
517
- @ Daily digest only</label><br></td>
673
+ @ <label><input type="checkbox" name="di" %s(PCK("di"))>\
674
+ @ Daily digest only</label><br>
675
+ if( g.perm.Admin ){
676
+ @ <label><input type="checkbox" name="vi" %s(PCK("vi"))>\
677
+ @ Verified</label><br>
678
+ @ <label><input type="checkbox" name="dnc" %s(PCK("dnc"))>\
679
+ @ Do not call</label><br>
680
+ }
681
+ @ </td>
518682
@ </tr>
519683
@ <tr>
520684
@ <td></td>
521
- @ <td><input type="submit" value="Submit"></td>
685
+ @ <td><input type="submit" name="submit" value="Submit"></td>
522686
@ </tr>
523687
@ </table>
524688
if( needCaptcha ){
525689
@ <div class="captcha"><table class="captcha"><tr><td><pre>
526690
@ %h(zCaptcha)
@@ -527,10 +691,11 @@
527691
@ </pre>
528692
@ Enter the 8 characters above in the "Security Code" box
529693
@ </td></tr></table></div>
530694
}
531695
@ </form>
696
+ fossil_free(zErr);
532697
style_footer();
533698
}
534699
535700
/*
536701
** WEBPAGE: alerts
537702
--- src/email.c
+++ src/email.c
@@ -48,11 +48,11 @@
48 @ -- notifications for specific branches or tags or tickets.
49 @ --
50 @ CREATE TABLE repository.subscriber(
51 @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use
52 @ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use
53 @ semail TEXT, -- email address
54 @ suname TEXT, -- corresponding USER entry
55 @ sverify BOOLEAN, -- email address verified
56 @ sdonotcall BOOLEAN, -- true for Do Not Call
57 @ sdigest BOOLEAN, -- true for daily digests only
58 @ ssub TEXT, -- baseline subscriptions
@@ -449,10 +449,100 @@
449 }
450 else{
451 usage("reset|send|setting");
452 }
453 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
455 /*
456 ** WEBPAGE: subscribe
457 **
458 ** Allow users to subscribe to email notifications, or to change or
@@ -461,66 +551,140 @@
461 void subscribe_page(void){
462 int needCaptcha;
463 unsigned int uSeed;
464 const char *zDecoded;
465 char *zCaptcha;
 
 
466
467 login_check_credentials();
468 if( !g.perm.EmailAlert ){
469 login_needed(g.anon.EmailAlert);
470 return;
471 }
472 if( login_is_individual()
473 && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
474 ){
 
 
 
475 cgi_redirect("%R/alerts");
476 return;
477 }
478 style_header("Email Subscription");
479 needCaptcha = P("usecaptcha")!=0 || !login_is_individual();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480 form_begin(0, "%R/subscribe");
481 @ <table class="subscribe">
482 @ <tr>
483 @ <td class="form_label">Email&nbsp;Address:</td>
484 @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
485 @ <td></td>
 
 
486 @ </tr>
487 if( needCaptcha ){
488 uSeed = captcha_seed();
489 zDecoded = captcha_decode(uSeed);
490 zCaptcha = captcha_render(zDecoded);
491 @ <tr>
492 @ <td class="form_label">Security Code:</td>
493 @ <td><input type="text" name="captcha" value="" size="30">
494 @ <input type="hidden" name="usecaptcha" value="1"></td>
495 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
 
 
 
496 @ </tr>
497 }
498 if( g.perm.Admin ){
499 @ <tr>
500 @ <td class="form_label">User:</td>
501 @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
502 @ size="30"></td>
503 @ <td></td>
 
 
504 @ </tr>
505 }
506 @ <tr>
507 @ <td class="form_label">Options:</td>
508 @ <td><label><input type="checkbox" name="sa" value="%d(P10("sa"))">\
509 @ Announcements</label><br>
510 @ <label><input type="checkbox" name="sc" value="%d(P01("sc"))">\
511 @ Check-ins</label><br>
512 @ <label><input type="checkbox" name="st" value="%d(P01("st"))">\
513 @ Ticket changes</label><br>
514 @ <label><input type="checkbox" name="sw" value="%d(P01("sw"))">\
515 @ Wiki</label><br>
516 @ <label><input type="checkbox" name="di" value="%d(P01("di"))">\
517 @ Daily digest only</label><br></td>
 
 
 
 
 
 
 
518 @ </tr>
519 @ <tr>
520 @ <td></td>
521 @ <td><input type="submit" value="Submit"></td>
522 @ </tr>
523 @ </table>
524 if( needCaptcha ){
525 @ <div class="captcha"><table class="captcha"><tr><td><pre>
526 @ %h(zCaptcha)
@@ -527,10 +691,11 @@
527 @ </pre>
528 @ Enter the 8 characters above in the "Security Code" box
529 @ </td></tr></table></div>
530 }
531 @ </form>
 
532 style_footer();
533 }
534
535 /*
536 ** WEBPAGE: alerts
537
--- src/email.c
+++ src/email.c
@@ -48,11 +48,11 @@
48 @ -- notifications for specific branches or tags or tickets.
49 @ --
50 @ CREATE TABLE repository.subscriber(
51 @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use
52 @ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use
53 @ semail TEXT UNIQUE COLLATE nocase,-- email address
54 @ suname TEXT, -- corresponding USER entry
55 @ sverify BOOLEAN, -- email address verified
56 @ sdonotcall BOOLEAN, -- true for Do Not Call
57 @ sdigest BOOLEAN, -- true for daily digests only
58 @ ssub TEXT, -- baseline subscriptions
@@ -449,10 +449,100 @@
449 }
450 else{
451 usage("reset|send|setting");
452 }
453 }
454
455 /*
456 ** Do error checking on a submitted subscription form. Return TRUE
457 ** if the submission is valid. Return false if any problems are seen.
458 */
459 static int subscribe_error_check(
460 int *peErr, /* Type of error */
461 char **pzErr, /* Error message text */
462 int needCaptcha /* True if captcha check needed */
463 ){
464 const char *zEAddr;
465 int i, j, n;
466 char c;
467
468 *peErr = 0;
469 *pzErr = 0;
470
471 /* Check the validity of the email address.
472 **
473 ** (1) Exactly one '@' character.
474 ** (2) No other characters besides [a-zA-Z0-9._-]
475 */
476 zEAddr = P("e");
477 if( zEAddr==0 ) return 0;
478 for(i=j=0; (c = zEAddr[i])!=0; i++){
479 if( c=='@' ){
480 n = i;
481 j++;
482 continue;
483 }
484 if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' ){
485 *peErr = 1;
486 *pzErr = mprintf("illegal character in email address: 0x%x '%c'",
487 c, c);
488 return 0;
489 }
490 }
491 if( j!=1 ){
492 *peErr = 1;
493 *pzErr = mprintf("email address should contain exactly one '@'");
494 return 0;
495 }
496 if( n<1 ){
497 *peErr = 1;
498 *pzErr = mprintf("name missing before '@' in email address");
499 return 0;
500 }
501 if( n>i-5 ){
502 *peErr = 1;
503 *pzErr = mprintf("email domain too short");
504 return 0;
505 }
506
507 /* Verify the captcha */
508 if( needCaptcha && !captcha_is_correct(1) ){
509 *peErr = 2;
510 *pzErr = mprintf("incorrect security code");
511 return 0;
512 }
513
514 /* Check to make sure the email address is available for reuse */
515 if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
516 *peErr = 1;
517 *pzErr = mprintf("this email address is used by someone else");
518 return 0;
519 }
520
521 /* If we reach this point, all is well */
522 return 1;
523 }
524
525 /*
526 ** Text of email message sent in order to confirm a subscription.
527 */
528 static const char zConfirmMsg[] =
529 @ Someone has signed you up for email alerts on the Fossil repository
530 @ at %R.
531 @
532 @ To confirm your subscription and begin receiving alerts, click on
533 @ the following hyperlink:
534 @
535 @ %R/alerts/%s
536 @
537 @ Save the hyperlink above! You can reuse this same hyperlink to
538 @ unsubscribe or to change the kinds of alerts you receive.
539 @
540 @ If you do not want to subscribe, you can simply ignore this message.
541 @ You will not be contacted again.
542 @
543 ;
544
545 /*
546 ** WEBPAGE: subscribe
547 **
548 ** Allow users to subscribe to email notifications, or to change or
@@ -461,66 +551,140 @@
551 void subscribe_page(void){
552 int needCaptcha;
553 unsigned int uSeed;
554 const char *zDecoded;
555 char *zCaptcha;
556 char *zErr = 0;
557 int eErr = 0;
558
559 login_check_credentials();
560 if( !g.perm.EmailAlert ){
561 login_needed(g.anon.EmailAlert);
562 return;
563 }
564 if( login_is_individual()
565 && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
566 ){
567 /* This person is already signed up for email alerts. Jump
568 ** to the screen that lets them edit their alert preferences.
569 */
570 cgi_redirect("%R/alerts");
571 return;
572 }
573 needCaptcha = !login_is_individual();
574 if( P("submit")
575 && cgi_csrf_safe(1)
576 && subscribe_error_check(&eErr,&zErr,needCaptcha)
577 ){
578 /* A validated request for a new subscription has been received. */
579 char ssub[20];
580 const char *zEAddr = P("e");
581 sqlite3_int64 id; /* New subscriber Id */
582 const char *zCode; /* New subscriber code (in hex) */
583 int nsub = 0;
584 if( PB("sa") ) ssub[nsub++] = 'a';
585 if( PB("sc") ) ssub[nsub++] = 'c';
586 if( PB("st") ) ssub[nsub++] = 't';
587 if( PB("sw") ) ssub[nsub++] = 'w';
588 ssub[nsub] = 0;
589 db_multi_exec(
590 "INSERT INTO subscriber(subscriberCode,semail,suname,"
591 " sverify,sdonotcall,sdigest,ssub,sctime,smtime,smip)"
592 "VALUES(randomblob(32),%Q,%Q,%d,0,%d,%Q,"
593 " julianday('now'),julianday('now'),%Q)",
594 /* semail */ zEAddr,
595 /* suname */ needCaptcha==0 ? g.zLogin : 0,
596 /* sverify */ needCaptcha==0,
597 /* sdigest */ PB("di"),
598 /* ssub */ ssub,
599 /* smip */ g.zIpAddr
600 );
601 id = db_last_insert_rowid();
602 zCode = db_text(0,
603 "SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld",
604 id);
605 if( !needCaptcha ){
606 /* The new subscription has been added on behalf of a logged-in user.
607 ** No verification is required. Jump immediately to /alerts page.
608 */
609 cgi_redirectf("%R/alerts/%s", zCode);
610 return;
611 }else{
612 /* We need to send a verification email */
613 Blob hdr, body;
614 blob_init(&hdr,0,0);
615 blob_init(&body,0,0);
616 blob_appendf(&hdr, "To: %s\n", zEAddr);
617 blob_appendf(&hdr, "Subject: Subscription verification\n");
618 blob_appendf(&body, zConfirmMsg/*works-like:"%s"*/, zCode);
619 email_send(&hdr, &body, 0, 0);
620 style_header("Email Alert Verification");
621 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
622 @ hyperlink that you must click on in order to activate your
623 @ subscription.</p>
624 style_footer();
625 }
626 return;
627 }
628 style_header("Signup For Email Alerts");
629 @ <p>To receive email notifications for changes to this
630 @ repository, fill out the form below and press "Submit" button.</p>
631 form_begin(0, "%R/subscribe");
632 @ <table class="subscribe">
633 @ <tr>
634 @ <td class="form_label">Email&nbsp;Address:</td>
635 @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
636 if( eErr==1 ){
637 @ <td><span class="loginError">&larr; %h(zErr)</span></td>
638 }
639 @ </tr>
640 if( needCaptcha ){
641 uSeed = captcha_seed();
642 zDecoded = captcha_decode(uSeed);
643 zCaptcha = captcha_render(zDecoded);
644 @ <tr>
645 @ <td class="form_label">Security Code:</td>
646 @ <td><input type="text" name="captcha" value="" size="30">
 
647 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
648 if( eErr==2 ){
649 @ <td><span class="loginError">&larr; %h(zErr)</span></td>
650 }
651 @ </tr>
652 }
653 if( g.perm.Admin ){
654 @ <tr>
655 @ <td class="form_label">User:</td>
656 @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
657 @ size="30"></td>
658 if( eErr==3 ){
659 @ <td><span class="loginError">&larr; %h(zErr)</span></td>
660 }
661 @ </tr>
662 }
663 @ <tr>
664 @ <td class="form_label">Options:</td>
665 @ <td><label><input type="checkbox" name="sa" %s(PCK("sa"))>\
666 @ Announcements</label><br>
667 @ <label><input type="checkbox" name="sc" %s(PCK("sc"))>\
668 @ Check-ins</label><br>
669 @ <label><input type="checkbox" name="st" %s(PCK("st"))>\
670 @ Ticket changes</label><br>
671 @ <label><input type="checkbox" name="sw" %s(PCK("sw"))>\
672 @ Wiki</label><br>
673 @ <label><input type="checkbox" name="di" %s(PCK("di"))>\
674 @ Daily digest only</label><br>
675 if( g.perm.Admin ){
676 @ <label><input type="checkbox" name="vi" %s(PCK("vi"))>\
677 @ Verified</label><br>
678 @ <label><input type="checkbox" name="dnc" %s(PCK("dnc"))>\
679 @ Do not call</label><br>
680 }
681 @ </td>
682 @ </tr>
683 @ <tr>
684 @ <td></td>
685 @ <td><input type="submit" name="submit" value="Submit"></td>
686 @ </tr>
687 @ </table>
688 if( needCaptcha ){
689 @ <div class="captcha"><table class="captcha"><tr><td><pre>
690 @ %h(zCaptcha)
@@ -527,10 +691,11 @@
691 @ </pre>
692 @ Enter the 8 characters above in the "Security Code" box
693 @ </td></tr></table></div>
694 }
695 @ </form>
696 fossil_free(zErr);
697 style_footer();
698 }
699
700 /*
701 ** WEBPAGE: alerts
702
+1 -1
--- src/tkt.c
+++ src/tkt.c
@@ -594,11 +594,11 @@
594594
int nJ = 0;
595595
Blob tktchng, cksum;
596596
int needMod;
597597
598598
login_verify_csrf_secret();
599
- if( !captcha_is_correct() ){
599
+ if( !captcha_is_correct(0) ){
600600
@ <p class="generalError">Error: Incorrect security code.</p>
601601
return TH_OK;
602602
}
603603
zUuid = (const char *)pUuid;
604604
blob_zero(&tktchng);
605605
--- src/tkt.c
+++ src/tkt.c
@@ -594,11 +594,11 @@
594 int nJ = 0;
595 Blob tktchng, cksum;
596 int needMod;
597
598 login_verify_csrf_secret();
599 if( !captcha_is_correct() ){
600 @ <p class="generalError">Error: Incorrect security code.</p>
601 return TH_OK;
602 }
603 zUuid = (const char *)pUuid;
604 blob_zero(&tktchng);
605
--- src/tkt.c
+++ src/tkt.c
@@ -594,11 +594,11 @@
594 int nJ = 0;
595 Blob tktchng, cksum;
596 int needMod;
597
598 login_verify_csrf_secret();
599 if( !captcha_is_correct(0) ){
600 @ <p class="generalError">Error: Incorrect security code.</p>
601 return TH_OK;
602 }
603 zUuid = (const char *)pUuid;
604 blob_zero(&tktchng);
605
+2 -2
--- src/wiki.c
+++ src/wiki.c
@@ -534,11 +534,11 @@
534534
zBody = pWiki->zWiki;
535535
zMimetype = pWiki->zMimetype;
536536
}
537537
}
538538
if( P("submit")!=0 && zBody!=0
539
- && (goodCaptcha = captcha_is_correct())
539
+ && (goodCaptcha = captcha_is_correct(0))
540540
){
541541
char *zDate;
542542
Blob cksum;
543543
blob_zero(&wiki);
544544
db_begin_transaction();
@@ -758,11 +758,11 @@
758758
if( !g.perm.ApndWiki ){
759759
login_needed(g.anon.ApndWiki);
760760
return;
761761
}
762762
if( P("submit")!=0 && P("r")!=0 && P("u")!=0
763
- && (goodCaptcha = captcha_is_correct())
763
+ && (goodCaptcha = captcha_is_correct(0))
764764
){
765765
char *zDate;
766766
Blob cksum;
767767
Blob body;
768768
Blob wiki;
769769
--- src/wiki.c
+++ src/wiki.c
@@ -534,11 +534,11 @@
534 zBody = pWiki->zWiki;
535 zMimetype = pWiki->zMimetype;
536 }
537 }
538 if( P("submit")!=0 && zBody!=0
539 && (goodCaptcha = captcha_is_correct())
540 ){
541 char *zDate;
542 Blob cksum;
543 blob_zero(&wiki);
544 db_begin_transaction();
@@ -758,11 +758,11 @@
758 if( !g.perm.ApndWiki ){
759 login_needed(g.anon.ApndWiki);
760 return;
761 }
762 if( P("submit")!=0 && P("r")!=0 && P("u")!=0
763 && (goodCaptcha = captcha_is_correct())
764 ){
765 char *zDate;
766 Blob cksum;
767 Blob body;
768 Blob wiki;
769
--- src/wiki.c
+++ src/wiki.c
@@ -534,11 +534,11 @@
534 zBody = pWiki->zWiki;
535 zMimetype = pWiki->zMimetype;
536 }
537 }
538 if( P("submit")!=0 && zBody!=0
539 && (goodCaptcha = captcha_is_correct(0))
540 ){
541 char *zDate;
542 Blob cksum;
543 blob_zero(&wiki);
544 db_begin_transaction();
@@ -758,11 +758,11 @@
758 if( !g.perm.ApndWiki ){
759 login_needed(g.anon.ApndWiki);
760 return;
761 }
762 if( P("submit")!=0 && P("r")!=0 && P("u")!=0
763 && (goodCaptcha = captcha_is_correct(0))
764 ){
765 char *zDate;
766 Blob cksum;
767 Blob body;
768 Blob wiki;
769

Keyboard Shortcuts

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