Fossil SCM

Non-working code for the /subscribe and /alerts web pages. This is an incremental check-in.

drh 2018-06-21 12:34 UTC trunk
Commit e91143e8132cb42e0fbec2645a023a7fed59f3b07c3642ed2112cd6163b56515
+11
--- src/cgi.c
+++ src/cgi.c
@@ -56,10 +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)
6163
6264
6365
/*
6466
** Destinations for output text.
6567
*/
@@ -1118,10 +1120,19 @@
11181120
int cgi_parameter_boolean(const char *zName){
11191121
const char *zIn = cgi_parameter(zName, 0);
11201122
if( zIn==0 ) return 0;
11211123
return zIn[0]==0 || is_truth(zIn);
11221124
}
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
+}
11231134
11241135
/*
11251136
** Return the name of the i-th CGI parameter. Return NULL if there
11261137
** are fewer than i registered CGI parameters.
11271138
*/
11281139
--- src/cgi.c
+++ src/cgi.c
@@ -56,10 +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
62
63 /*
64 ** Destinations for output text.
65 */
@@ -1118,10 +1120,19 @@
1118 int cgi_parameter_boolean(const char *zName){
1119 const char *zIn = cgi_parameter(zName, 0);
1120 if( zIn==0 ) return 0;
1121 return zIn[0]==0 || is_truth(zIn);
1122 }
 
 
 
 
 
 
 
 
 
1123
1124 /*
1125 ** Return the name of the i-th CGI parameter. Return NULL if there
1126 ** are fewer than i registered CGI parameters.
1127 */
1128
--- src/cgi.c
+++ src/cgi.c
@@ -56,10 +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 */
@@ -1118,10 +1120,19 @@
1120 int cgi_parameter_boolean(const char *zName){
1121 const char *zIn = cgi_parameter(zName, 0);
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 */
1139
+28
--- src/db.c
+++ src/db.c
@@ -875,10 +875,36 @@
875875
}else{
876876
sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
877877
}
878878
}
879879
880
+/*
881
+** If the input is a hexadecimal string, convert that string into a BLOB.
882
+** If the input is not a hexadecimal string, return NULL.
883
+*/
884
+void db_hextoblob(
885
+ sqlite3_context *context,
886
+ int argc,
887
+ sqlite3_value **argv
888
+){
889
+ const unsigned char *zIn = sqlite3_value_text(argv[0]);
890
+ int nIn = sqlite3_value_bytes(argv[0]);
891
+ unsigned char *zOut;
892
+ if( zIn==0 ) return;
893
+ if( nIn&1 ) return;
894
+ if( !validate16(zIn, nIn) ) return;
895
+ zOut = sqlite3_malloc64( nIn/2 );
896
+ if( zOut==0 ){
897
+ sqlite3_result_error_nomem(context);
898
+ return;
899
+ }
900
+ decode16(zIn, zOut, nIn);
901
+ sqlite3_result_blob(context, zOut, nIn/2, sqlite3_free);
902
+}
903
+
904
+/*
905
+**
880906
881907
/*
882908
** Register the SQL functions that are useful both to the internal
883909
** representation and to the "fossil sql" command.
884910
*/
@@ -893,10 +919,12 @@
893919
db_now_function, 0, 0);
894920
sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
895921
db_tolocal_function, 0, 0);
896922
sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
897923
db_fromlocal_function, 0, 0);
924
+ sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
925
+ db_hextoblob, 0, 0);
898926
}
899927
900928
#if USE_SEE
901929
/*
902930
** This is a pointer to the saved database encryption key string.
903931
--- src/db.c
+++ src/db.c
@@ -875,10 +875,36 @@
875 }else{
876 sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
877 }
878 }
879
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
880
881 /*
882 ** Register the SQL functions that are useful both to the internal
883 ** representation and to the "fossil sql" command.
884 */
@@ -893,10 +919,12 @@
893 db_now_function, 0, 0);
894 sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
895 db_tolocal_function, 0, 0);
896 sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
897 db_fromlocal_function, 0, 0);
 
 
898 }
899
900 #if USE_SEE
901 /*
902 ** This is a pointer to the saved database encryption key string.
903
--- src/db.c
+++ src/db.c
@@ -875,10 +875,36 @@
875 }else{
876 sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
877 }
878 }
879
880 /*
881 ** If the input is a hexadecimal string, convert that string into a BLOB.
882 ** If the input is not a hexadecimal string, return NULL.
883 */
884 void db_hextoblob(
885 sqlite3_context *context,
886 int argc,
887 sqlite3_value **argv
888 ){
889 const unsigned char *zIn = sqlite3_value_text(argv[0]);
890 int nIn = sqlite3_value_bytes(argv[0]);
891 unsigned char *zOut;
892 if( zIn==0 ) return;
893 if( nIn&1 ) return;
894 if( !validate16(zIn, nIn) ) return;
895 zOut = sqlite3_malloc64( nIn/2 );
896 if( zOut==0 ){
897 sqlite3_result_error_nomem(context);
898 return;
899 }
900 decode16(zIn, zOut, nIn);
901 sqlite3_result_blob(context, zOut, nIn/2, sqlite3_free);
902 }
903
904 /*
905 **
906
907 /*
908 ** Register the SQL functions that are useful both to the internal
909 ** representation and to the "fossil sql" command.
910 */
@@ -893,10 +919,12 @@
919 db_now_function, 0, 0);
920 sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
921 db_tolocal_function, 0, 0);
922 sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
923 db_fromlocal_function, 0, 0);
924 sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
925 db_hextoblob, 0, 0);
926 }
927
928 #if USE_SEE
929 /*
930 ** This is a pointer to the saved database encryption key string.
931
+186 -32
--- src/email.c
+++ src/email.c
@@ -19,10 +19,15 @@
1919
*/
2020
#include "config.h"
2121
#include "email.h"
2222
#include <assert.h>
2323
24
+/*
25
+** Maximum size of the subscriberCode blob, in bytes
26
+*/
27
+#define SUBSCRIBER_CODE_SZ 32
28
+
2429
/*
2530
** SQL code to implement the tables needed by the email notification
2631
** system.
2732
*/
2833
static const char zEmailInit[] =
@@ -42,23 +47,23 @@
4247
@ -- we might also add a separate table that allows subscribing to email
4348
@ -- notifications for specific branches or tags or tickets.
4449
@ --
4550
@ CREATE TABLE repository.subscriber(
4651
@ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use
47
-@ subscriberCode TEXT UNIQUE, -- UUID for subscriber. External use
48
-@ sname TEXT, -- Human readable name
49
-@ suname TEXT, -- Corresponding USER or NULL
52
+@ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use
5053
@ semail TEXT, -- email address
54
+@ suname TEXT, -- corresponding USER entry
5155
@ sverify BOOLEAN, -- email address verified
5256
@ sdonotcall BOOLEAN, -- true for Do Not Call
5357
@ sdigest BOOLEAN, -- true for daily digests only
5458
@ ssub TEXT, -- baseline subscriptions
5559
@ sctime DATE, -- When this entry was created. JulianDay
5660
@ smtime DATE, -- Last change. JulianDay
57
-@ sipaddr TEXT, -- IP address for last change
58
-@ spswdHash TEXT -- SHA3 hash of password
61
+@ smip TEXT -- IP address of last change
5962
@ );
63
+@ CREATE INDEX repository.subscriberUname
64
+@ ON subscriber(suname) WHERE suname IS NOT NULL;
6065
@
6166
@ -- Email notifications that need to be sent.
6267
@ --
6368
@ -- If the eventid key is an integer, then it corresponds to the
6469
@ -- EVENT.OBJID table. Other kinds of eventids are reserved for
@@ -454,19 +459,24 @@
454459
455460
login_check_credentials();
456461
if( !g.perm.EmailAlert ){
457462
login_needed(g.anon.EmailAlert);
458463
return;
464
+ }
465
+ if( login_is_individual()
466
+ && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
467
+ ){
468
+ cgi_redirect("%R/alerts");
469
+ return;
459470
}
460471
style_header("Email Subscription");
461
- needCaptcha = P("usecaptcha")!=0 || login_is_nobody()
462
- || login_is_special(g.zLogin);
472
+ needCaptcha = P("usecaptcha")!=0 || !login_is_individual();
463473
form_begin(0, "%R/subscribe");
464474
@ <table class="subscribe">
465475
@ <tr>
466476
@ <td class="form_label">Email&nbsp;Address:</td>
467
- @ <td><input type="text" name="e" value="" size="30"></td>
477
+ @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
468478
@ <td></td>
469479
@ </tr>
470480
if( needCaptcha ){
471481
uSeed = captcha_seed();
472482
zDecoded = captcha_decode(uSeed);
@@ -474,34 +484,31 @@
474484
@ <tr>
475485
@ <td class="form_label">Security Code:</td>
476486
@ <td><input type="text" name="captcha" value="" size="30">
477487
@ <input type="hidden" name="usecaptcha" value="1"></td>
478488
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
479
- @ <td><span class="optionalTag">(copy from below)</span></td>
480
- @ </tr>
481
- }
482
- @ <tr>
483
- @ <td class="form_label">Nickname:</td>
484
- @ <td><input type="text" name="nn" value="" size="30"></td>
485
- @ <td><span class="optionalTag">(optional)</span></td>
486
- @ </tr>
487
- @ <tr>
488
- @ <td class="form_label">Password:</td>
489
- @ <td><input type="password" name="pw" value="" size="30"></td>
490
- @ <td><span class="optionalTag">(optional)</span></td>
491
- @ </tr>
492
- @ <tr>
493
- @ <td class="form_label">Options:</td>
494
- @ <td><label><input type="checkbox" name="sa" value="0">\
495
- @ Announcements</label><br>
496
- @ <label><input type="checkbox" name="sc" value="0">\
497
- @ Check-ins</label><br>
498
- @ <label><input type="checkbox" name="st" value="0">\
499
- @ Ticket changes</label><br>
500
- @ <label><input type="checkbox" name="sw" value="0">\
501
- @ Wiki</label><br>
502
- @ <label><input type="checkbox" name="di" value="0">\
489
+ @ </tr>
490
+ }
491
+ if( g.perm.Admin ){
492
+ @ <tr>
493
+ @ <td class="form_label">User:</td>
494
+ @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
495
+ @ size="30"></td>
496
+ @ <td></td>
497
+ @ </tr>
498
+ }
499
+ @ <tr>
500
+ @ <td class="form_label">Options:</td>
501
+ @ <td><label><input type="checkbox" name="sa" value="%d(P10("sa"))">\
502
+ @ Announcements</label><br>
503
+ @ <label><input type="checkbox" name="sc" value="%d(P01("sc"))">\
504
+ @ Check-ins</label><br>
505
+ @ <label><input type="checkbox" name="st" value="%d(P01("st"))">\
506
+ @ Ticket changes</label><br>
507
+ @ <label><input type="checkbox" name="sw" value="%d(P01("sw"))">\
508
+ @ Wiki</label><br>
509
+ @ <label><input type="checkbox" name="di" value="%d(P01("di"))">\
503510
@ Daily digest only</label><br></td>
504511
@ </tr>
505512
@ <tr>
506513
@ <td></td>
507514
@ <td><input type="submit" value="Submit"></td>
@@ -515,5 +522,152 @@
515522
@ </td></tr></table></div>
516523
}
517524
@ </form>
518525
style_footer();
519526
}
527
+
528
+/*
529
+** WEBPAGE: alerts
530
+**
531
+** Edit email alert and notification settings.
532
+**
533
+** The subscriber entry is identified in either of two ways:
534
+**
535
+** (1) The name= query parameter contains the subscriberCode.
536
+**
537
+** (2) The user is logged into an account other than "nobody" or
538
+** "anonymous". In that case the notification settings
539
+** associated with that account can be edited without needing
540
+** to know the subscriber code.
541
+*/
542
+void alerts_page(void){
543
+ const char *zName = P("name");
544
+ Stmt q;
545
+ int sa, sc, st, sw;
546
+ int sdigest, sdonotcall, sverify;
547
+ const char *ssub;
548
+ const char *semail;
549
+ const char *sctime;
550
+ const char *smtime;
551
+ const char *smip;
552
+ int i;
553
+
554
+
555
+ login_check_credentials();
556
+ if( !g.perm.EmailAlert ){
557
+ cgi_redirect("subscribe");
558
+ return;
559
+ }
560
+ if( zName==0 && login_is_individual() ){
561
+ zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
562
+ " WHERE suname=%Q", g.zLogin);
563
+ }
564
+ if( zName==0 || !validate16(zName, -1) ){
565
+ cgi_redirect("subscribe");
566
+ return;
567
+ }
568
+ if( P("submit")!=0 && cgi_csrf_safe(1) ){
569
+ int sdonotcall = PB("sdonotcall");
570
+ int sdigest = PB("sdigest");
571
+ char ssub[10];
572
+ int nsub = 0;
573
+ if( PB("sa") ) ssub[nsub++] = 'a';
574
+ if( PB("sc") ) ssub[nsub++] = 'c';
575
+ if( PB("st") ) ssub[nsub++] = 't';
576
+ if( PB("sw") ) ssub[nsub++] = 'w';
577
+ ssub[nsub] = 0;
578
+ db_multi_exec(
579
+ "UPDATE subscriber SET"
580
+ " sdonotcall=%d,"
581
+ " sdigest=%d,"
582
+ " ssub=%Q,"
583
+ " smtime=julianday('now'),"
584
+ " smip=%Q"
585
+ " WHERE subscriberCode=hextoblob(%Q)",
586
+ sdonotcall,
587
+ sdigest,
588
+ ssub,
589
+ g.zIpAddr,
590
+ zName
591
+ );
592
+ }
593
+ if( PB("dodelete") && P("delete")!=0 && cgi_csrf_safe(1) ){
594
+ db_multi_exec(
595
+ "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
596
+ zName
597
+ );
598
+ }
599
+ db_prepare(&q,
600
+ "SELECT"
601
+ " semail,"
602
+ " sverify,"
603
+ " sdonotcall,"
604
+ " sdigest,"
605
+ " ssub,"
606
+ " datetime(sctime),"
607
+ " datetime(smtime),"
608
+ " smip"
609
+ " FROM subscriber WHERE subscriberCode=hextoblob(%Q)", zName);
610
+ if( db_step(&q)!=SQLITE_ROW ){
611
+ db_finalize(&q);
612
+ cgi_redirect("subscribe");
613
+ return;
614
+ }
615
+ style_header("Update Subscription");
616
+ semail = db_column_text(&q, 0);
617
+ sverify = db_column_int(&q, 1);
618
+ sdonotcall = db_column_int(&q, 2);
619
+ sdigest = db_column_int(&q, 3);
620
+ ssub = db_column_text(&q, 4);
621
+ sa = strchr(ssub,'a')!=0;
622
+ sc = strchr(ssub,'c')!=0;
623
+ st = strchr(ssub,'t')!=0;
624
+ sw = strchr(ssub,'w')!=0;
625
+ sctime = db_column_text(&q, 5);
626
+ smtime = db_column_text(&q, 6);
627
+ smip = db_column_text(&q, 7);
628
+ form_begin(0, "%R/alerts");
629
+ @ <table class="subscribe">
630
+ @ <tr>
631
+ @ <td class="form_label">Email&nbsp;Address:</td>
632
+ @ <td>%h(semail)</td>
633
+ @ </tr>
634
+ if( g.perm.Admin ){
635
+ @ <tr>
636
+ @ <td class='form_label'>IP Address:</td>
637
+ @ <td>%h(smip)</td>
638
+ @ </tr>
639
+ }
640
+ @ <tr>
641
+ @ <td class="form_label">Options:</td>
642
+ @ <td><label><input type="checkbox" name="sa" value="%d(sa)">\
643
+ @ Announcements</label><br>
644
+ @ <label><input type="checkbox" name="sc" value="%d(sc)">\
645
+ @ Check-ins</label><br>
646
+ @ <label><input type="checkbox" name="st" value="%d(st)">\
647
+ @ Ticket changes</label><br>
648
+ @ <label><input type="checkbox" name="sw" value="%d(sw)">\
649
+ @ Wiki</label><br>
650
+ @ <label><input type="checkbox" name="sdigest" value="%d(sdigest)">\
651
+ @ Daily digest only</label><br>
652
+ if( g.perm.Admin ){
653
+ @ <label><input type="checkbox" name="sdonotcall" value="%d(sdonotcall)">\
654
+ @ Do not call</label><br>
655
+ @ <label><input type="checkbox" name="sverify" value="%d(sverify)">\
656
+ @ Verified</label><br>
657
+ }
658
+ @ </td></tr>
659
+ @ <tr>
660
+ @ <td></td>
661
+ @ <td><input type="submit" value="Submit"></td>
662
+ @ </tr>
663
+ @ <tr>
664
+ @ <td></td>
665
+ @ <td><label><input type="checkbox" name="dodelete" value="0">
666
+ @ Delete this subscription</label>
667
+ @ <input type="submit" name="delete" value="Delete"></td>
668
+ @ </tr>
669
+ @ </table>
670
+ @ </form>
671
+ db_finalize(&q);
672
+ style_footer();
673
+}
520674
--- src/email.c
+++ src/email.c
@@ -19,10 +19,15 @@
19 */
20 #include "config.h"
21 #include "email.h"
22 #include <assert.h>
23
 
 
 
 
 
24 /*
25 ** SQL code to implement the tables needed by the email notification
26 ** system.
27 */
28 static const char zEmailInit[] =
@@ -42,23 +47,23 @@
42 @ -- we might also add a separate table that allows subscribing to email
43 @ -- notifications for specific branches or tags or tickets.
44 @ --
45 @ CREATE TABLE repository.subscriber(
46 @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use
47 @ subscriberCode TEXT UNIQUE, -- UUID for subscriber. External use
48 @ sname TEXT, -- Human readable name
49 @ suname TEXT, -- Corresponding USER or NULL
50 @ semail TEXT, -- email address
 
51 @ sverify BOOLEAN, -- email address verified
52 @ sdonotcall BOOLEAN, -- true for Do Not Call
53 @ sdigest BOOLEAN, -- true for daily digests only
54 @ ssub TEXT, -- baseline subscriptions
55 @ sctime DATE, -- When this entry was created. JulianDay
56 @ smtime DATE, -- Last change. JulianDay
57 @ sipaddr TEXT, -- IP address for last change
58 @ spswdHash TEXT -- SHA3 hash of password
59 @ );
 
 
60 @
61 @ -- Email notifications that need to be sent.
62 @ --
63 @ -- If the eventid key is an integer, then it corresponds to the
64 @ -- EVENT.OBJID table. Other kinds of eventids are reserved for
@@ -454,19 +459,24 @@
454
455 login_check_credentials();
456 if( !g.perm.EmailAlert ){
457 login_needed(g.anon.EmailAlert);
458 return;
 
 
 
 
 
 
459 }
460 style_header("Email Subscription");
461 needCaptcha = P("usecaptcha")!=0 || login_is_nobody()
462 || login_is_special(g.zLogin);
463 form_begin(0, "%R/subscribe");
464 @ <table class="subscribe">
465 @ <tr>
466 @ <td class="form_label">Email&nbsp;Address:</td>
467 @ <td><input type="text" name="e" value="" size="30"></td>
468 @ <td></td>
469 @ </tr>
470 if( needCaptcha ){
471 uSeed = captcha_seed();
472 zDecoded = captcha_decode(uSeed);
@@ -474,34 +484,31 @@
474 @ <tr>
475 @ <td class="form_label">Security Code:</td>
476 @ <td><input type="text" name="captcha" value="" size="30">
477 @ <input type="hidden" name="usecaptcha" value="1"></td>
478 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
479 @ <td><span class="optionalTag">(copy from below)</span></td>
480 @ </tr>
481 }
482 @ <tr>
483 @ <td class="form_label">Nickname:</td>
484 @ <td><input type="text" name="nn" value="" size="30"></td>
485 @ <td><span class="optionalTag">(optional)</span></td>
486 @ </tr>
487 @ <tr>
488 @ <td class="form_label">Password:</td>
489 @ <td><input type="password" name="pw" value="" size="30"></td>
490 @ <td><span class="optionalTag">(optional)</span></td>
491 @ </tr>
492 @ <tr>
493 @ <td class="form_label">Options:</td>
494 @ <td><label><input type="checkbox" name="sa" value="0">\
495 @ Announcements</label><br>
496 @ <label><input type="checkbox" name="sc" value="0">\
497 @ Check-ins</label><br>
498 @ <label><input type="checkbox" name="st" value="0">\
499 @ Ticket changes</label><br>
500 @ <label><input type="checkbox" name="sw" value="0">\
501 @ Wiki</label><br>
502 @ <label><input type="checkbox" name="di" value="0">\
503 @ Daily digest only</label><br></td>
504 @ </tr>
505 @ <tr>
506 @ <td></td>
507 @ <td><input type="submit" value="Submit"></td>
@@ -515,5 +522,152 @@
515 @ </td></tr></table></div>
516 }
517 @ </form>
518 style_footer();
519 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
--- src/email.c
+++ src/email.c
@@ -19,10 +19,15 @@
19 */
20 #include "config.h"
21 #include "email.h"
22 #include <assert.h>
23
24 /*
25 ** Maximum size of the subscriberCode blob, in bytes
26 */
27 #define SUBSCRIBER_CODE_SZ 32
28
29 /*
30 ** SQL code to implement the tables needed by the email notification
31 ** system.
32 */
33 static const char zEmailInit[] =
@@ -42,23 +47,23 @@
47 @ -- we might also add a separate table that allows subscribing to email
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
59 @ sctime DATE, -- When this entry was created. JulianDay
60 @ smtime DATE, -- Last change. JulianDay
61 @ smip TEXT -- IP address of last change
 
62 @ );
63 @ CREATE INDEX repository.subscriberUname
64 @ ON subscriber(suname) WHERE suname IS NOT NULL;
65 @
66 @ -- Email notifications that need to be sent.
67 @ --
68 @ -- If the eventid key is an integer, then it corresponds to the
69 @ -- EVENT.OBJID table. Other kinds of eventids are reserved for
@@ -454,19 +459,24 @@
459
460 login_check_credentials();
461 if( !g.perm.EmailAlert ){
462 login_needed(g.anon.EmailAlert);
463 return;
464 }
465 if( login_is_individual()
466 && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin)
467 ){
468 cgi_redirect("%R/alerts");
469 return;
470 }
471 style_header("Email Subscription");
472 needCaptcha = P("usecaptcha")!=0 || !login_is_individual();
 
473 form_begin(0, "%R/subscribe");
474 @ <table class="subscribe">
475 @ <tr>
476 @ <td class="form_label">Email&nbsp;Address:</td>
477 @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
478 @ <td></td>
479 @ </tr>
480 if( needCaptcha ){
481 uSeed = captcha_seed();
482 zDecoded = captcha_decode(uSeed);
@@ -474,34 +484,31 @@
484 @ <tr>
485 @ <td class="form_label">Security Code:</td>
486 @ <td><input type="text" name="captcha" value="" size="30">
487 @ <input type="hidden" name="usecaptcha" value="1"></td>
488 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
489 @ </tr>
490 }
491 if( g.perm.Admin ){
492 @ <tr>
493 @ <td class="form_label">User:</td>
494 @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \
495 @ size="30"></td>
496 @ <td></td>
497 @ </tr>
498 }
499 @ <tr>
500 @ <td class="form_label">Options:</td>
501 @ <td><label><input type="checkbox" name="sa" value="%d(P10("sa"))">\
502 @ Announcements</label><br>
503 @ <label><input type="checkbox" name="sc" value="%d(P01("sc"))">\
504 @ Check-ins</label><br>
505 @ <label><input type="checkbox" name="st" value="%d(P01("st"))">\
506 @ Ticket changes</label><br>
507 @ <label><input type="checkbox" name="sw" value="%d(P01("sw"))">\
508 @ Wiki</label><br>
509 @ <label><input type="checkbox" name="di" value="%d(P01("di"))">\
 
 
 
510 @ Daily digest only</label><br></td>
511 @ </tr>
512 @ <tr>
513 @ <td></td>
514 @ <td><input type="submit" value="Submit"></td>
@@ -515,5 +522,152 @@
522 @ </td></tr></table></div>
523 }
524 @ </form>
525 style_footer();
526 }
527
528 /*
529 ** WEBPAGE: alerts
530 **
531 ** Edit email alert and notification settings.
532 **
533 ** The subscriber entry is identified in either of two ways:
534 **
535 ** (1) The name= query parameter contains the subscriberCode.
536 **
537 ** (2) The user is logged into an account other than "nobody" or
538 ** "anonymous". In that case the notification settings
539 ** associated with that account can be edited without needing
540 ** to know the subscriber code.
541 */
542 void alerts_page(void){
543 const char *zName = P("name");
544 Stmt q;
545 int sa, sc, st, sw;
546 int sdigest, sdonotcall, sverify;
547 const char *ssub;
548 const char *semail;
549 const char *sctime;
550 const char *smtime;
551 const char *smip;
552 int i;
553
554
555 login_check_credentials();
556 if( !g.perm.EmailAlert ){
557 cgi_redirect("subscribe");
558 return;
559 }
560 if( zName==0 && login_is_individual() ){
561 zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
562 " WHERE suname=%Q", g.zLogin);
563 }
564 if( zName==0 || !validate16(zName, -1) ){
565 cgi_redirect("subscribe");
566 return;
567 }
568 if( P("submit")!=0 && cgi_csrf_safe(1) ){
569 int sdonotcall = PB("sdonotcall");
570 int sdigest = PB("sdigest");
571 char ssub[10];
572 int nsub = 0;
573 if( PB("sa") ) ssub[nsub++] = 'a';
574 if( PB("sc") ) ssub[nsub++] = 'c';
575 if( PB("st") ) ssub[nsub++] = 't';
576 if( PB("sw") ) ssub[nsub++] = 'w';
577 ssub[nsub] = 0;
578 db_multi_exec(
579 "UPDATE subscriber SET"
580 " sdonotcall=%d,"
581 " sdigest=%d,"
582 " ssub=%Q,"
583 " smtime=julianday('now'),"
584 " smip=%Q"
585 " WHERE subscriberCode=hextoblob(%Q)",
586 sdonotcall,
587 sdigest,
588 ssub,
589 g.zIpAddr,
590 zName
591 );
592 }
593 if( PB("dodelete") && P("delete")!=0 && cgi_csrf_safe(1) ){
594 db_multi_exec(
595 "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
596 zName
597 );
598 }
599 db_prepare(&q,
600 "SELECT"
601 " semail,"
602 " sverify,"
603 " sdonotcall,"
604 " sdigest,"
605 " ssub,"
606 " datetime(sctime),"
607 " datetime(smtime),"
608 " smip"
609 " FROM subscriber WHERE subscriberCode=hextoblob(%Q)", zName);
610 if( db_step(&q)!=SQLITE_ROW ){
611 db_finalize(&q);
612 cgi_redirect("subscribe");
613 return;
614 }
615 style_header("Update Subscription");
616 semail = db_column_text(&q, 0);
617 sverify = db_column_int(&q, 1);
618 sdonotcall = db_column_int(&q, 2);
619 sdigest = db_column_int(&q, 3);
620 ssub = db_column_text(&q, 4);
621 sa = strchr(ssub,'a')!=0;
622 sc = strchr(ssub,'c')!=0;
623 st = strchr(ssub,'t')!=0;
624 sw = strchr(ssub,'w')!=0;
625 sctime = db_column_text(&q, 5);
626 smtime = db_column_text(&q, 6);
627 smip = db_column_text(&q, 7);
628 form_begin(0, "%R/alerts");
629 @ <table class="subscribe">
630 @ <tr>
631 @ <td class="form_label">Email&nbsp;Address:</td>
632 @ <td>%h(semail)</td>
633 @ </tr>
634 if( g.perm.Admin ){
635 @ <tr>
636 @ <td class='form_label'>IP Address:</td>
637 @ <td>%h(smip)</td>
638 @ </tr>
639 }
640 @ <tr>
641 @ <td class="form_label">Options:</td>
642 @ <td><label><input type="checkbox" name="sa" value="%d(sa)">\
643 @ Announcements</label><br>
644 @ <label><input type="checkbox" name="sc" value="%d(sc)">\
645 @ Check-ins</label><br>
646 @ <label><input type="checkbox" name="st" value="%d(st)">\
647 @ Ticket changes</label><br>
648 @ <label><input type="checkbox" name="sw" value="%d(sw)">\
649 @ Wiki</label><br>
650 @ <label><input type="checkbox" name="sdigest" value="%d(sdigest)">\
651 @ Daily digest only</label><br>
652 if( g.perm.Admin ){
653 @ <label><input type="checkbox" name="sdonotcall" value="%d(sdonotcall)">\
654 @ Do not call</label><br>
655 @ <label><input type="checkbox" name="sverify" value="%d(sverify)">\
656 @ Verified</label><br>
657 }
658 @ </td></tr>
659 @ <tr>
660 @ <td></td>
661 @ <td><input type="submit" value="Submit"></td>
662 @ </tr>
663 @ <tr>
664 @ <td></td>
665 @ <td><label><input type="checkbox" name="dodelete" value="0">
666 @ Delete this subscription</label>
667 @ <input type="submit" name="delete" value="Delete"></td>
668 @ </tr>
669 @ </table>
670 @ </form>
671 db_finalize(&q);
672 style_footer();
673 }
674
--- src/encode.c
+++ src/encode.c
@@ -634,10 +634,11 @@
634634
** Return true if the input string contains only valid base-16 digits.
635635
** If any invalid characters appear in the string, return false.
636636
*/
637637
int validate16(const char *zIn, int nIn){
638638
int i;
639
+ if( nIn<0 ) nIn = (int)strlen(zIn);
639640
for(i=0; i<nIn; i++, zIn++){
640641
if( zDecode[zIn[0]&0xff]>63 ){
641642
return zIn[0]==0;
642643
}
643644
}
644645
--- src/encode.c
+++ src/encode.c
@@ -634,10 +634,11 @@
634 ** Return true if the input string contains only valid base-16 digits.
635 ** If any invalid characters appear in the string, return false.
636 */
637 int validate16(const char *zIn, int nIn){
638 int i;
 
639 for(i=0; i<nIn; i++, zIn++){
640 if( zDecode[zIn[0]&0xff]>63 ){
641 return zIn[0]==0;
642 }
643 }
644
--- src/encode.c
+++ src/encode.c
@@ -634,10 +634,11 @@
634 ** Return true if the input string contains only valid base-16 digits.
635 ** If any invalid characters appear in the string, return false.
636 */
637 int validate16(const char *zIn, int nIn){
638 int i;
639 if( nIn<0 ) nIn = (int)strlen(zIn);
640 for(i=0; i<nIn; i++, zIn++){
641 if( zDecode[zIn[0]&0xff]>63 ){
642 return zIn[0]==0;
643 }
644 }
645
--- src/login.c
+++ src/login.c
@@ -1348,10 +1348,19 @@
13481348
** Return true if the user is "nobody"
13491349
*/
13501350
int login_is_nobody(void){
13511351
return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0;
13521352
}
1353
+
1354
+/*
1355
+** Return true if the user is a specific individual, not "nobody" or
1356
+** "anonymous".
1357
+*/
1358
+int login_is_individual(void){
1359
+ return g.zLogin!=0 && g.zLogin[0]!=0 && fossil_strcmp(g.zLogin,"nobody")!=0
1360
+ && fossil_strcmp(g.zLogin,"anonymous")!=0;
1361
+}
13531362
13541363
/*
13551364
** Return the login name. If no login name is specified, return "nobody".
13561365
*/
13571366
const char *login_name(void){
13581367
--- src/login.c
+++ src/login.c
@@ -1348,10 +1348,19 @@
1348 ** Return true if the user is "nobody"
1349 */
1350 int login_is_nobody(void){
1351 return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0;
1352 }
 
 
 
 
 
 
 
 
 
1353
1354 /*
1355 ** Return the login name. If no login name is specified, return "nobody".
1356 */
1357 const char *login_name(void){
1358
--- src/login.c
+++ src/login.c
@@ -1348,10 +1348,19 @@
1348 ** Return true if the user is "nobody"
1349 */
1350 int login_is_nobody(void){
1351 return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0;
1352 }
1353
1354 /*
1355 ** Return true if the user is a specific individual, not "nobody" or
1356 ** "anonymous".
1357 */
1358 int login_is_individual(void){
1359 return g.zLogin!=0 && g.zLogin[0]!=0 && fossil_strcmp(g.zLogin,"nobody")!=0
1360 && fossil_strcmp(g.zLogin,"anonymous")!=0;
1361 }
1362
1363 /*
1364 ** Return the login name. If no login name is specified, return "nobody".
1365 */
1366 const char *login_name(void){
1367

Keyboard Shortcuts

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