Fossil SCM
Rework the internal email sending logic so that it is connection-oriented. This makes it more efficient and makes it easier to add support for an SMTP sending method at a later date.
Commit
b42189878e66cfd569298fa26a4a73a3face1fd4a3c4ec193ab13b4da408339f
Parent
2c3b903043c65cb…
1 file changed
+200
-104
+200
-104
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -284,19 +284,147 @@ | ||
| 284 | 284 | # undef popen |
| 285 | 285 | # define popen _popen |
| 286 | 286 | # undef pclose |
| 287 | 287 | # define pclose _pclose |
| 288 | 288 | #endif |
| 289 | + | |
| 290 | +#if INTERFACE | |
| 291 | +/* | |
| 292 | +** An instance of the following object is used to send emails. | |
| 293 | +*/ | |
| 294 | +struct EmailSender { | |
| 295 | + sqlite3 *db; /* Database emails are sent to */ | |
| 296 | + sqlite3_stmt *pStmt; /* Stmt to insert into the database */ | |
| 297 | + const char *zDest; /* How to send email. */ | |
| 298 | + const char *zDb; /* Name of database file */ | |
| 299 | + const char *zDir; /* Directory in which to store as email files */ | |
| 300 | + const char *zCmd; /* Command to run for each email */ | |
| 301 | + const char *zFrom; /* Emails come from here */ | |
| 302 | + char *zErr; /* Error message */ | |
| 303 | + int bImmediateFail; /* On any error, call fossil_fatal() */ | |
| 304 | +}; | |
| 305 | +#endif /* INTERFACE */ | |
| 306 | + | |
| 307 | +/* | |
| 308 | +** Shutdown an emailer. Clear all information other than the error message. | |
| 309 | +*/ | |
| 310 | +static void emailerShutdown(EmailSender *p){ | |
| 311 | + sqlite3_finalize(p->pStmt); | |
| 312 | + p->pStmt = 0; | |
| 313 | + sqlite3_free(p->db); | |
| 314 | + p->db = 0; | |
| 315 | + p->zDb = 0; | |
| 316 | + p->zDir = 0; | |
| 317 | + p->zCmd = 0; | |
| 318 | + p->zDest = "off"; | |
| 319 | +} | |
| 320 | + | |
| 321 | +/* | |
| 322 | +** Put the EmailSender into an error state. | |
| 323 | +*/ | |
| 324 | +static void emailerError(EmailSender *p, const char *zFormat, ...){ | |
| 325 | + va_list ap; | |
| 326 | + fossil_free(p->zErr); | |
| 327 | + va_start(ap, zFormat); | |
| 328 | + p->zErr = vmprintf(zFormat, ap); | |
| 329 | + va_end(ap); | |
| 330 | + emailerShutdown(p); | |
| 331 | + if( p->bImmediateFail ){ | |
| 332 | + fossil_fatal("%s", p->zErr); | |
| 333 | + } | |
| 334 | +} | |
| 335 | + | |
| 336 | +/* | |
| 337 | +** Free an email sender object | |
| 338 | +*/ | |
| 339 | +void email_sender_free(EmailSender *p){ | |
| 340 | + emailerShutdown(p); | |
| 341 | + fossil_free(p->zErr); | |
| 342 | + fossil_free(p); | |
| 343 | +} | |
| 344 | + | |
| 345 | +/* | |
| 346 | +** Get an email setting value. Report an error if not configured. | |
| 347 | +** Return 0 on success and one if there is an error. | |
| 348 | +*/ | |
| 349 | +static int emailerGetSetting( | |
| 350 | + EmailSender *p, /* Where to report the error */ | |
| 351 | + const char **pzVal, /* Write the setting value here */ | |
| 352 | + const char *zName /* Name of the setting */ | |
| 353 | +){ | |
| 354 | + const char *z = db_get(zName, 0); | |
| 355 | + int rc = 0; | |
| 356 | + if( z==0 || z[0]==0 ){ | |
| 357 | + emailerError(p, "missing \"%s\" setting", zName); | |
| 358 | + rc = 1; | |
| 359 | + }else{ | |
| 360 | + *pzVal = z; | |
| 361 | + } | |
| 362 | + return rc; | |
| 363 | +} | |
| 289 | 364 | |
| 290 | 365 | /* |
| 291 | -** Send an email message using whatever sending mechanism is configured | |
| 292 | -** by these settings: | |
| 366 | +** Create a new EmailSender object. | |
| 367 | +** | |
| 368 | +** The method used for sending email is determined by various email-* | |
| 369 | +** settings, and especially email-send-method. The repository | |
| 370 | +** email-send-method can be overridden by the zAltDest argument to | |
| 371 | +** cause a different sending mechanism to be used. Pass "stdout" to | |
| 372 | +** zAltDest to cause all emails to be printed to the console for | |
| 373 | +** debugging purposes. | |
| 293 | 374 | ** |
| 294 | -** email-send-method "off" Do not send any emails | |
| 295 | -** "pipe" Pipe the email to email-send-command | |
| 296 | -** "db" Store the mail in database email-send-db | |
| 297 | -** "file" Store the email as a file in email-send-dir | |
| 375 | +** The EmailSender object returned must be freed using email_sender_free(). | |
| 376 | +*/ | |
| 377 | +EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){ | |
| 378 | + EmailSender *p; | |
| 379 | + char *zMissing = 0; | |
| 380 | + | |
| 381 | + p = fossil_malloc(sizeof(*p)); | |
| 382 | + memset(p, 0, sizeof(*p)); | |
| 383 | + p->bImmediateFail = bImmediateFail; | |
| 384 | + if( zAltDest ){ | |
| 385 | + p->zDest = zAltDest; | |
| 386 | + }else{ | |
| 387 | + p->zDest = db_get("email-send-method","off"); | |
| 388 | + } | |
| 389 | + if( fossil_strcmp(p->zDest,"off")==0 ) return p; | |
| 390 | + if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p; | |
| 391 | + if( fossil_strcmp(p->zDest,"db")==0 ){ | |
| 392 | + char *zErr; | |
| 393 | + int rc; | |
| 394 | + if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p; | |
| 395 | + rc = sqlite3_open(p->zDb, &p->db); | |
| 396 | + if( rc ){ | |
| 397 | + emailerError(p, "unable to open output database file \"%s\": %s", | |
| 398 | + p->zDb, sqlite3_errmsg(p->db)); | |
| 399 | + return p; | |
| 400 | + } | |
| 401 | + rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n" | |
| 402 | + " emailid INTEGER PRIMARY KEY,\n" | |
| 403 | + " msg TEXT\n);", 0, 0, &zErr); | |
| 404 | + if( zErr ){ | |
| 405 | + emailerError(p, "CREATE TABLE failed with \"%s\"", zErr); | |
| 406 | + sqlite3_free(zErr); | |
| 407 | + return p; | |
| 408 | + } | |
| 409 | + rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1, | |
| 410 | + &p->pStmt, 0); | |
| 411 | + if( rc ){ | |
| 412 | + emailerError(p, "cannot prepare INSERT statement: %s", | |
| 413 | + sqlite3_errmsg(p->db)); | |
| 414 | + return p; | |
| 415 | + } | |
| 416 | + }else if( fossil_strcmp(p->zDest, "pipe")==0 ){ | |
| 417 | + emailerGetSetting(p, &p->zCmd, "email-send-command"); | |
| 418 | + }else if( fossil_strcmp(p->zDest, "dir")==0 ){ | |
| 419 | + emailerGetSetting(p, &p->zDir, "email-send-dir"); | |
| 420 | + } | |
| 421 | + return p; | |
| 422 | +} | |
| 423 | + | |
| 424 | +/* | |
| 425 | +** Send a single email message. | |
| 298 | 426 | ** |
| 299 | 427 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| 300 | 428 | ** in the header. Likewise, the header must contains a "Subject:" line. |
| 301 | 429 | ** The header might also include fields like "Message-Id:" or |
| 302 | 430 | ** "In-Reply-To:". |
| @@ -305,97 +433,50 @@ | ||
| 305 | 433 | ** |
| 306 | 434 | ** From: |
| 307 | 435 | ** Content-Type: |
| 308 | 436 | ** Content-Transfer-Encoding: |
| 309 | 437 | ** |
| 310 | -** At least one body must be supplied. | |
| 311 | -** | |
| 312 | 438 | ** The caller maintains ownership of the input Blobs. This routine will |
| 313 | 439 | ** read the Blobs and send them onward to the email system, but it will |
| 314 | 440 | ** not free them. |
| 315 | -** | |
| 316 | -** If zDest is not NULL then it is an overwrite for the email-send-method. | |
| 317 | -** zDest can be "stdout" to send output to the console for debugging. | |
| 318 | 441 | */ |
| 319 | -void email_send(Blob *pHdr, Blob *pPlain, Blob *pHtml, const char *zDest){ | |
| 320 | - const char *zFrom = db_get("email-self", 0); | |
| 321 | - char *zBoundary = 0; | |
| 442 | +void email_send(EmailSender *p, Blob *pHdr, Blob *pBody){ | |
| 322 | 443 | Blob all; |
| 323 | - if( zFrom==0 ){ | |
| 324 | - fossil_warning("Missing configuration: \"email-self\""); | |
| 325 | - return; | |
| 326 | - } | |
| 327 | - if( zDest==0 ) zDest = db_get("email-send-method", "off"); | |
| 328 | - if( strcmp(zDest, "off")==0 ){ | |
| 444 | + if( fossil_strcmp(p->zDest, "off")==0 ){ | |
| 329 | 445 | return; |
| 330 | 446 | } |
| 331 | 447 | blob_init(&all, 0, 0); |
| 332 | 448 | blob_append(&all, blob_buffer(pHdr), blob_size(pHdr)); |
| 333 | - blob_appendf(&all, "From: %s\r\n", zFrom); | |
| 334 | - if( pPlain && pHtml ){ | |
| 335 | - blob_appendf(&all, "MIME-Version: 1.0\r\n"); | |
| 336 | - zBoundary = db_text(0, "SELECT hex(randomblob(20))"); | |
| 337 | - blob_appendf(&all, "Content-Type: multipart/alternative;" | |
| 338 | - " boundary=\"%s\"\r\n", zBoundary); | |
| 339 | - } | |
| 340 | - if( pPlain ){ | |
| 341 | - blob_add_final_newline(pPlain); | |
| 342 | - if( zBoundary ){ | |
| 343 | - blob_appendf(&all, "\r\n--%s\r\n", zBoundary); | |
| 344 | - } | |
| 345 | - blob_appendf(&all,"Content-Type: text/plain\r\n"); | |
| 346 | - blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); | |
| 347 | - append_base64(&all, pPlain); | |
| 348 | - } | |
| 349 | - if( pHtml ){ | |
| 350 | - blob_add_final_newline(pHtml); | |
| 351 | - if( zBoundary ){ | |
| 352 | - blob_appendf(&all, "--%s\r\n", zBoundary); | |
| 353 | - } | |
| 354 | - blob_appendf(&all,"Content-Type: text/html\r\n"); | |
| 355 | - blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); | |
| 356 | - append_base64(&all, pHtml); | |
| 357 | - } | |
| 358 | - if( zBoundary ){ | |
| 359 | - blob_appendf(&all, "--%s--\r\n", zBoundary); | |
| 360 | - fossil_free(zBoundary); | |
| 361 | - zBoundary = 0; | |
| 362 | - } | |
| 363 | - if( strcmp(zDest, "db")==0 ){ | |
| 364 | - sqlite3 *db; | |
| 365 | - sqlite3_stmt *pStmt; | |
| 366 | - int rc; | |
| 367 | - const char *zDb = db_get("email-send-db",0); | |
| 368 | - rc = sqlite3_open(zDb, &db); | |
| 369 | - if( rc==SQLITE_OK ){ | |
| 370 | - sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS email(\n" | |
| 371 | - " emailid INTEGER PRIMARY KEY,\n" | |
| 372 | - " msg TEXT\n);", 0, 0, 0); | |
| 373 | - rc = sqlite3_prepare_v2(db, "INSERT INTO email(msg) VALUES(?1)", -1, | |
| 374 | - &pStmt, 0); | |
| 375 | - if( rc==SQLITE_OK ){ | |
| 376 | - sqlite3_bind_text(pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); | |
| 377 | - sqlite3_step(pStmt); | |
| 378 | - sqlite3_finalize(pStmt); | |
| 379 | - } | |
| 380 | - sqlite3_close(db); | |
| 381 | - } | |
| 382 | - }else if( strcmp(zDest, "pipe")==0 ){ | |
| 383 | - const char *zCmd = db_get("email-send-command", 0); | |
| 384 | - if( zCmd ){ | |
| 385 | - FILE *out = popen(zCmd, "w"); | |
| 386 | - if( out ){ | |
| 387 | - fwrite(blob_buffer(&all), 1, blob_size(&all), out); | |
| 388 | - fclose(out); | |
| 389 | - } | |
| 390 | - } | |
| 391 | - }else if( strcmp(zDest, "dir")==0 ){ | |
| 392 | - const char *zDir = db_get("email-send-dir","./"); | |
| 393 | - char *zFile = emailTempFilename(zDir); | |
| 449 | + blob_appendf(&all, "From: %s\r\n", p->zFrom); | |
| 450 | + blob_add_final_newline(pBody); | |
| 451 | + blob_appendf(&all,"Content-Type: text/plain\r\n"); | |
| 452 | + blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); | |
| 453 | + append_base64(&all, pBody); | |
| 454 | + if( p->pStmt ){ | |
| 455 | + int i, rc; | |
| 456 | + sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); | |
| 457 | + for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){ | |
| 458 | + sqlite3_sleep(10); | |
| 459 | + } | |
| 460 | + rc = sqlite3_reset(p->pStmt); | |
| 461 | + if( rc!=SQLITE_OK ){ | |
| 462 | + emailerError(p, "Failed to insert email message into output queue.\n" | |
| 463 | + "%s", sqlite3_errmsg(p->db)); | |
| 464 | + } | |
| 465 | + }else if( p->zCmd ){ | |
| 466 | + FILE *out = popen(p->zCmd, "w"); | |
| 467 | + if( out ){ | |
| 468 | + fwrite(blob_buffer(&all), 1, blob_size(&all), out); | |
| 469 | + fclose(out); | |
| 470 | + }else{ | |
| 471 | + emailerError(p, "Could not open output pipe \"%s\"", p->zCmd); | |
| 472 | + } | |
| 473 | + }else if( p->zDir ){ | |
| 474 | + char *zFile = emailTempFilename(p->zDir); | |
| 394 | 475 | blob_write_to_file(&all, zFile); |
| 395 | 476 | fossil_free(zFile); |
| 396 | - }else if( strcmp(zDest, "stdout")==0 ){ | |
| 477 | + }else if( strcmp(p->zDest, "stdout")==0 ){ | |
| 397 | 478 | fossil_print("%s\n", blob_str(&all)); |
| 398 | 479 | } |
| 399 | 480 | blob_zero(&all); |
| 400 | 481 | } |
| 401 | 482 | |
| @@ -483,11 +564,10 @@ | ||
| 483 | 564 | ** email sending mechanism is currently configured. |
| 484 | 565 | ** Use this for testing the email configuration. |
| 485 | 566 | ** Options: |
| 486 | 567 | ** |
| 487 | 568 | ** --body FILENAME |
| 488 | -** --html | |
| 489 | 569 | ** --stdout |
| 490 | 570 | ** --subject|-S SUBJECT |
| 491 | 571 | ** |
| 492 | 572 | ** settings [NAME VALUE] With no arguments, list all email settings. |
| 493 | 573 | ** Or change the value of a single email setting. |
| @@ -556,16 +636,15 @@ | ||
| 556 | 636 | email_schema(); |
| 557 | 637 | } |
| 558 | 638 | }else |
| 559 | 639 | if( strncmp(zCmd, "send", nCmd)==0 ){ |
| 560 | 640 | Blob prompt, body, hdr; |
| 561 | - int sendAsBoth = find_option("both",0,0)!=0; | |
| 562 | - int sendAsHtml = find_option("html",0,0)!=0; | |
| 563 | 641 | const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; |
| 564 | 642 | int i; |
| 565 | 643 | const char *zSubject = find_option("subject", "S", 1); |
| 566 | 644 | const char *zSource = find_option("body", 0, 1); |
| 645 | + EmailSender *pSender; | |
| 567 | 646 | verify_all_options(); |
| 568 | 647 | blob_init(&prompt, 0, 0); |
| 569 | 648 | blob_init(&body, 0, 0); |
| 570 | 649 | blob_init(&hdr, 0, 0); |
| 571 | 650 | for(i=3; i<g.argc; i++){ |
| @@ -578,21 +657,13 @@ | ||
| 578 | 657 | blob_read_from_file(&body, zSource, ExtFILE); |
| 579 | 658 | }else{ |
| 580 | 659 | prompt_for_user_comment(&body, &prompt); |
| 581 | 660 | } |
| 582 | 661 | blob_add_final_newline(&body); |
| 583 | - if( sendAsHtml ){ | |
| 584 | - email_send(&hdr, 0, &body, zDest); | |
| 585 | - }else if( sendAsBoth ){ | |
| 586 | - Blob html; | |
| 587 | - blob_init(&html, 0, 0); | |
| 588 | - blob_appendf(&html, "<pre>\n%h</pre>\n", blob_str(&body)); | |
| 589 | - email_send(&hdr, &body, &html, zDest); | |
| 590 | - blob_zero(&html); | |
| 591 | - }else{ | |
| 592 | - email_send(&hdr, &body, 0, zDest); | |
| 593 | - } | |
| 662 | + pSender = email_sender_new(zDest, 1); | |
| 663 | + email_send(pSender, &hdr, &body); | |
| 664 | + email_sender_free(pSender); | |
| 594 | 665 | blob_zero(&hdr); |
| 595 | 666 | blob_zero(&body); |
| 596 | 667 | blob_zero(&prompt); |
| 597 | 668 | }else |
| 598 | 669 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| @@ -822,21 +893,32 @@ | ||
| 822 | 893 | cgi_redirectf("%R/alerts/%s", zCode); |
| 823 | 894 | return; |
| 824 | 895 | }else{ |
| 825 | 896 | /* We need to send a verification email */ |
| 826 | 897 | Blob hdr, body; |
| 898 | + EmailSender *pSender = email_sender_new(0,0); | |
| 827 | 899 | blob_init(&hdr,0,0); |
| 828 | 900 | blob_init(&body,0,0); |
| 829 | 901 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 830 | 902 | blob_appendf(&hdr, "Subject: Subscription verification\n"); |
| 831 | 903 | blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/, |
| 832 | 904 | g.zBaseURL, g.zBaseURL, zCode); |
| 833 | - email_send(&hdr, &body, 0, 0); | |
| 905 | + email_send(pSender, &hdr, &body); | |
| 834 | 906 | style_header("Email Alert Verification"); |
| 835 | - @ <p>An email has been sent to "%h(zEAddr)". That email contains a | |
| 836 | - @ hyperlink that you must click on in order to activate your | |
| 837 | - @ subscription.</p> | |
| 907 | + if( pSender->zErr ){ | |
| 908 | + @ <h1>Internal Error</h1> | |
| 909 | + @ <p>The following internal error was encountered while trying | |
| 910 | + @ to send the confirmation email: | |
| 911 | + @ <blockquote><pre> | |
| 912 | + @ %h(pSender->zErr) | |
| 913 | + @ </pre></blockquote> | |
| 914 | + }else{ | |
| 915 | + @ <p>An email has been sent to "%h(zEAddr)". That email contains a | |
| 916 | + @ hyperlink that you must click on in order to activate your | |
| 917 | + @ subscription.</p> | |
| 918 | + } | |
| 919 | + email_sender_free(pSender); | |
| 838 | 920 | style_footer(); |
| 839 | 921 | } |
| 840 | 922 | return; |
| 841 | 923 | } |
| 842 | 924 | style_header("Signup For Email Alerts"); |
| @@ -1213,20 +1295,31 @@ | ||
| 1213 | 1295 | } |
| 1214 | 1296 | if( bSubmit ){ |
| 1215 | 1297 | /* If we get this far, it means that a valid unsubscribe request has |
| 1216 | 1298 | ** been submitted. Send the appropriate email. */ |
| 1217 | 1299 | Blob hdr, body; |
| 1300 | + EmailSender *pSender = email_sender_new(0,0); | |
| 1218 | 1301 | blob_init(&hdr,0,0); |
| 1219 | 1302 | blob_init(&body,0,0); |
| 1220 | 1303 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 1221 | 1304 | blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n"); |
| 1222 | 1305 | blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/, |
| 1223 | 1306 | g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode); |
| 1224 | - email_send(&hdr, &body, 0, 0); | |
| 1307 | + email_send(pSender, &hdr, &body); | |
| 1225 | 1308 | style_header("Unsubscribe Instructions Sent"); |
| 1226 | - @ <p>An email has been sent to "%h(zEAddr)" that explains how to | |
| 1227 | - @ unsubscribe and/or modify your subscription settings</p> | |
| 1309 | + if( pSender->zErr ){ | |
| 1310 | + @ <h1>Internal Error</h1> | |
| 1311 | + @ <p>The following error was encountered while trying to send an | |
| 1312 | + @ email to %h(zEAddr): | |
| 1313 | + @ <blockquote><pre> | |
| 1314 | + @ %h(pSender->zErr) | |
| 1315 | + @ </pre></blockquote> | |
| 1316 | + }else{ | |
| 1317 | + @ <p>An email has been sent to "%h(zEAddr)" that explains how to | |
| 1318 | + @ unsubscribe and/or modify your subscription settings</p> | |
| 1319 | + } | |
| 1320 | + email_sender_free(pSender); | |
| 1228 | 1321 | style_footer(); |
| 1229 | 1322 | return; |
| 1230 | 1323 | } |
| 1231 | 1324 | |
| 1232 | 1325 | /* Non-logged-in users have to enter an email address to which is |
| @@ -1525,19 +1618,21 @@ | ||
| 1525 | 1618 | Blob hdr, body; |
| 1526 | 1619 | const char *zUrl; |
| 1527 | 1620 | const char *zRepoName; |
| 1528 | 1621 | const char *zFrom; |
| 1529 | 1622 | const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; |
| 1623 | + EmailSender *pSender = 0; | |
| 1530 | 1624 | |
| 1531 | 1625 | db_begin_transaction(); |
| 1532 | 1626 | if( !email_enabled() ) goto send_alerts_done; |
| 1533 | 1627 | zUrl = db_get("email-url",0); |
| 1534 | 1628 | if( zUrl==0 ) goto send_alerts_done; |
| 1535 | 1629 | zRepoName = db_get("email-subname",0); |
| 1536 | 1630 | if( zRepoName==0 ) goto send_alerts_done; |
| 1537 | 1631 | zFrom = db_get("email-self",0); |
| 1538 | 1632 | if( zFrom==0 ) goto send_alerts_done; |
| 1633 | + pSender = email_sender_new(zDest, 0); | |
| 1539 | 1634 | db_multi_exec( |
| 1540 | 1635 | "DROP TABLE IF EXISTS temp.wantalert;" |
| 1541 | 1636 | "CREATE TEMP TABLE wantalert(eventId TEXT);" |
| 1542 | 1637 | ); |
| 1543 | 1638 | if( flags & SENDALERT_DIGEST ){ |
| @@ -1587,11 +1682,11 @@ | ||
| 1587 | 1682 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1588 | 1683 | } |
| 1589 | 1684 | if( nHit==0 ) continue; |
| 1590 | 1685 | blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n", |
| 1591 | 1686 | '-', zUrl, zCode); |
| 1592 | - email_send(&hdr,&body,0,zDest); | |
| 1687 | + email_send(pSender,&hdr,&body); | |
| 1593 | 1688 | blob_truncate(&hdr); |
| 1594 | 1689 | blob_truncate(&body); |
| 1595 | 1690 | } |
| 1596 | 1691 | blob_zero(&hdr); |
| 1597 | 1692 | blob_zero(&body); |
| @@ -1604,7 +1699,8 @@ | ||
| 1604 | 1699 | db_multi_exec("UPDATE pending_alert SET sentSep=true"); |
| 1605 | 1700 | } |
| 1606 | 1701 | db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep"); |
| 1607 | 1702 | } |
| 1608 | 1703 | send_alerts_done: |
| 1704 | + email_sender_free(pSender); | |
| 1609 | 1705 | db_end_transaction(0); |
| 1610 | 1706 | } |
| 1611 | 1707 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -284,19 +284,147 @@ | |
| 284 | # undef popen |
| 285 | # define popen _popen |
| 286 | # undef pclose |
| 287 | # define pclose _pclose |
| 288 | #endif |
| 289 | |
| 290 | /* |
| 291 | ** Send an email message using whatever sending mechanism is configured |
| 292 | ** by these settings: |
| 293 | ** |
| 294 | ** email-send-method "off" Do not send any emails |
| 295 | ** "pipe" Pipe the email to email-send-command |
| 296 | ** "db" Store the mail in database email-send-db |
| 297 | ** "file" Store the email as a file in email-send-dir |
| 298 | ** |
| 299 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| 300 | ** in the header. Likewise, the header must contains a "Subject:" line. |
| 301 | ** The header might also include fields like "Message-Id:" or |
| 302 | ** "In-Reply-To:". |
| @@ -305,97 +433,50 @@ | |
| 305 | ** |
| 306 | ** From: |
| 307 | ** Content-Type: |
| 308 | ** Content-Transfer-Encoding: |
| 309 | ** |
| 310 | ** At least one body must be supplied. |
| 311 | ** |
| 312 | ** The caller maintains ownership of the input Blobs. This routine will |
| 313 | ** read the Blobs and send them onward to the email system, but it will |
| 314 | ** not free them. |
| 315 | ** |
| 316 | ** If zDest is not NULL then it is an overwrite for the email-send-method. |
| 317 | ** zDest can be "stdout" to send output to the console for debugging. |
| 318 | */ |
| 319 | void email_send(Blob *pHdr, Blob *pPlain, Blob *pHtml, const char *zDest){ |
| 320 | const char *zFrom = db_get("email-self", 0); |
| 321 | char *zBoundary = 0; |
| 322 | Blob all; |
| 323 | if( zFrom==0 ){ |
| 324 | fossil_warning("Missing configuration: \"email-self\""); |
| 325 | return; |
| 326 | } |
| 327 | if( zDest==0 ) zDest = db_get("email-send-method", "off"); |
| 328 | if( strcmp(zDest, "off")==0 ){ |
| 329 | return; |
| 330 | } |
| 331 | blob_init(&all, 0, 0); |
| 332 | blob_append(&all, blob_buffer(pHdr), blob_size(pHdr)); |
| 333 | blob_appendf(&all, "From: %s\r\n", zFrom); |
| 334 | if( pPlain && pHtml ){ |
| 335 | blob_appendf(&all, "MIME-Version: 1.0\r\n"); |
| 336 | zBoundary = db_text(0, "SELECT hex(randomblob(20))"); |
| 337 | blob_appendf(&all, "Content-Type: multipart/alternative;" |
| 338 | " boundary=\"%s\"\r\n", zBoundary); |
| 339 | } |
| 340 | if( pPlain ){ |
| 341 | blob_add_final_newline(pPlain); |
| 342 | if( zBoundary ){ |
| 343 | blob_appendf(&all, "\r\n--%s\r\n", zBoundary); |
| 344 | } |
| 345 | blob_appendf(&all,"Content-Type: text/plain\r\n"); |
| 346 | blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); |
| 347 | append_base64(&all, pPlain); |
| 348 | } |
| 349 | if( pHtml ){ |
| 350 | blob_add_final_newline(pHtml); |
| 351 | if( zBoundary ){ |
| 352 | blob_appendf(&all, "--%s\r\n", zBoundary); |
| 353 | } |
| 354 | blob_appendf(&all,"Content-Type: text/html\r\n"); |
| 355 | blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); |
| 356 | append_base64(&all, pHtml); |
| 357 | } |
| 358 | if( zBoundary ){ |
| 359 | blob_appendf(&all, "--%s--\r\n", zBoundary); |
| 360 | fossil_free(zBoundary); |
| 361 | zBoundary = 0; |
| 362 | } |
| 363 | if( strcmp(zDest, "db")==0 ){ |
| 364 | sqlite3 *db; |
| 365 | sqlite3_stmt *pStmt; |
| 366 | int rc; |
| 367 | const char *zDb = db_get("email-send-db",0); |
| 368 | rc = sqlite3_open(zDb, &db); |
| 369 | if( rc==SQLITE_OK ){ |
| 370 | sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS email(\n" |
| 371 | " emailid INTEGER PRIMARY KEY,\n" |
| 372 | " msg TEXT\n);", 0, 0, 0); |
| 373 | rc = sqlite3_prepare_v2(db, "INSERT INTO email(msg) VALUES(?1)", -1, |
| 374 | &pStmt, 0); |
| 375 | if( rc==SQLITE_OK ){ |
| 376 | sqlite3_bind_text(pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); |
| 377 | sqlite3_step(pStmt); |
| 378 | sqlite3_finalize(pStmt); |
| 379 | } |
| 380 | sqlite3_close(db); |
| 381 | } |
| 382 | }else if( strcmp(zDest, "pipe")==0 ){ |
| 383 | const char *zCmd = db_get("email-send-command", 0); |
| 384 | if( zCmd ){ |
| 385 | FILE *out = popen(zCmd, "w"); |
| 386 | if( out ){ |
| 387 | fwrite(blob_buffer(&all), 1, blob_size(&all), out); |
| 388 | fclose(out); |
| 389 | } |
| 390 | } |
| 391 | }else if( strcmp(zDest, "dir")==0 ){ |
| 392 | const char *zDir = db_get("email-send-dir","./"); |
| 393 | char *zFile = emailTempFilename(zDir); |
| 394 | blob_write_to_file(&all, zFile); |
| 395 | fossil_free(zFile); |
| 396 | }else if( strcmp(zDest, "stdout")==0 ){ |
| 397 | fossil_print("%s\n", blob_str(&all)); |
| 398 | } |
| 399 | blob_zero(&all); |
| 400 | } |
| 401 | |
| @@ -483,11 +564,10 @@ | |
| 483 | ** email sending mechanism is currently configured. |
| 484 | ** Use this for testing the email configuration. |
| 485 | ** Options: |
| 486 | ** |
| 487 | ** --body FILENAME |
| 488 | ** --html |
| 489 | ** --stdout |
| 490 | ** --subject|-S SUBJECT |
| 491 | ** |
| 492 | ** settings [NAME VALUE] With no arguments, list all email settings. |
| 493 | ** Or change the value of a single email setting. |
| @@ -556,16 +636,15 @@ | |
| 556 | email_schema(); |
| 557 | } |
| 558 | }else |
| 559 | if( strncmp(zCmd, "send", nCmd)==0 ){ |
| 560 | Blob prompt, body, hdr; |
| 561 | int sendAsBoth = find_option("both",0,0)!=0; |
| 562 | int sendAsHtml = find_option("html",0,0)!=0; |
| 563 | const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; |
| 564 | int i; |
| 565 | const char *zSubject = find_option("subject", "S", 1); |
| 566 | const char *zSource = find_option("body", 0, 1); |
| 567 | verify_all_options(); |
| 568 | blob_init(&prompt, 0, 0); |
| 569 | blob_init(&body, 0, 0); |
| 570 | blob_init(&hdr, 0, 0); |
| 571 | for(i=3; i<g.argc; i++){ |
| @@ -578,21 +657,13 @@ | |
| 578 | blob_read_from_file(&body, zSource, ExtFILE); |
| 579 | }else{ |
| 580 | prompt_for_user_comment(&body, &prompt); |
| 581 | } |
| 582 | blob_add_final_newline(&body); |
| 583 | if( sendAsHtml ){ |
| 584 | email_send(&hdr, 0, &body, zDest); |
| 585 | }else if( sendAsBoth ){ |
| 586 | Blob html; |
| 587 | blob_init(&html, 0, 0); |
| 588 | blob_appendf(&html, "<pre>\n%h</pre>\n", blob_str(&body)); |
| 589 | email_send(&hdr, &body, &html, zDest); |
| 590 | blob_zero(&html); |
| 591 | }else{ |
| 592 | email_send(&hdr, &body, 0, zDest); |
| 593 | } |
| 594 | blob_zero(&hdr); |
| 595 | blob_zero(&body); |
| 596 | blob_zero(&prompt); |
| 597 | }else |
| 598 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| @@ -822,21 +893,32 @@ | |
| 822 | cgi_redirectf("%R/alerts/%s", zCode); |
| 823 | return; |
| 824 | }else{ |
| 825 | /* We need to send a verification email */ |
| 826 | Blob hdr, body; |
| 827 | blob_init(&hdr,0,0); |
| 828 | blob_init(&body,0,0); |
| 829 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 830 | blob_appendf(&hdr, "Subject: Subscription verification\n"); |
| 831 | blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/, |
| 832 | g.zBaseURL, g.zBaseURL, zCode); |
| 833 | email_send(&hdr, &body, 0, 0); |
| 834 | style_header("Email Alert Verification"); |
| 835 | @ <p>An email has been sent to "%h(zEAddr)". That email contains a |
| 836 | @ hyperlink that you must click on in order to activate your |
| 837 | @ subscription.</p> |
| 838 | style_footer(); |
| 839 | } |
| 840 | return; |
| 841 | } |
| 842 | style_header("Signup For Email Alerts"); |
| @@ -1213,20 +1295,31 @@ | |
| 1213 | } |
| 1214 | if( bSubmit ){ |
| 1215 | /* If we get this far, it means that a valid unsubscribe request has |
| 1216 | ** been submitted. Send the appropriate email. */ |
| 1217 | Blob hdr, body; |
| 1218 | blob_init(&hdr,0,0); |
| 1219 | blob_init(&body,0,0); |
| 1220 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 1221 | blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n"); |
| 1222 | blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/, |
| 1223 | g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode); |
| 1224 | email_send(&hdr, &body, 0, 0); |
| 1225 | style_header("Unsubscribe Instructions Sent"); |
| 1226 | @ <p>An email has been sent to "%h(zEAddr)" that explains how to |
| 1227 | @ unsubscribe and/or modify your subscription settings</p> |
| 1228 | style_footer(); |
| 1229 | return; |
| 1230 | } |
| 1231 | |
| 1232 | /* Non-logged-in users have to enter an email address to which is |
| @@ -1525,19 +1618,21 @@ | |
| 1525 | Blob hdr, body; |
| 1526 | const char *zUrl; |
| 1527 | const char *zRepoName; |
| 1528 | const char *zFrom; |
| 1529 | const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; |
| 1530 | |
| 1531 | db_begin_transaction(); |
| 1532 | if( !email_enabled() ) goto send_alerts_done; |
| 1533 | zUrl = db_get("email-url",0); |
| 1534 | if( zUrl==0 ) goto send_alerts_done; |
| 1535 | zRepoName = db_get("email-subname",0); |
| 1536 | if( zRepoName==0 ) goto send_alerts_done; |
| 1537 | zFrom = db_get("email-self",0); |
| 1538 | if( zFrom==0 ) goto send_alerts_done; |
| 1539 | db_multi_exec( |
| 1540 | "DROP TABLE IF EXISTS temp.wantalert;" |
| 1541 | "CREATE TEMP TABLE wantalert(eventId TEXT);" |
| 1542 | ); |
| 1543 | if( flags & SENDALERT_DIGEST ){ |
| @@ -1587,11 +1682,11 @@ | |
| 1587 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1588 | } |
| 1589 | if( nHit==0 ) continue; |
| 1590 | blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n", |
| 1591 | '-', zUrl, zCode); |
| 1592 | email_send(&hdr,&body,0,zDest); |
| 1593 | blob_truncate(&hdr); |
| 1594 | blob_truncate(&body); |
| 1595 | } |
| 1596 | blob_zero(&hdr); |
| 1597 | blob_zero(&body); |
| @@ -1604,7 +1699,8 @@ | |
| 1604 | db_multi_exec("UPDATE pending_alert SET sentSep=true"); |
| 1605 | } |
| 1606 | db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep"); |
| 1607 | } |
| 1608 | send_alerts_done: |
| 1609 | db_end_transaction(0); |
| 1610 | } |
| 1611 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -284,19 +284,147 @@ | |
| 284 | # undef popen |
| 285 | # define popen _popen |
| 286 | # undef pclose |
| 287 | # define pclose _pclose |
| 288 | #endif |
| 289 | |
| 290 | #if INTERFACE |
| 291 | /* |
| 292 | ** An instance of the following object is used to send emails. |
| 293 | */ |
| 294 | struct EmailSender { |
| 295 | sqlite3 *db; /* Database emails are sent to */ |
| 296 | sqlite3_stmt *pStmt; /* Stmt to insert into the database */ |
| 297 | const char *zDest; /* How to send email. */ |
| 298 | const char *zDb; /* Name of database file */ |
| 299 | const char *zDir; /* Directory in which to store as email files */ |
| 300 | const char *zCmd; /* Command to run for each email */ |
| 301 | const char *zFrom; /* Emails come from here */ |
| 302 | char *zErr; /* Error message */ |
| 303 | int bImmediateFail; /* On any error, call fossil_fatal() */ |
| 304 | }; |
| 305 | #endif /* INTERFACE */ |
| 306 | |
| 307 | /* |
| 308 | ** Shutdown an emailer. Clear all information other than the error message. |
| 309 | */ |
| 310 | static void emailerShutdown(EmailSender *p){ |
| 311 | sqlite3_finalize(p->pStmt); |
| 312 | p->pStmt = 0; |
| 313 | sqlite3_free(p->db); |
| 314 | p->db = 0; |
| 315 | p->zDb = 0; |
| 316 | p->zDir = 0; |
| 317 | p->zCmd = 0; |
| 318 | p->zDest = "off"; |
| 319 | } |
| 320 | |
| 321 | /* |
| 322 | ** Put the EmailSender into an error state. |
| 323 | */ |
| 324 | static void emailerError(EmailSender *p, const char *zFormat, ...){ |
| 325 | va_list ap; |
| 326 | fossil_free(p->zErr); |
| 327 | va_start(ap, zFormat); |
| 328 | p->zErr = vmprintf(zFormat, ap); |
| 329 | va_end(ap); |
| 330 | emailerShutdown(p); |
| 331 | if( p->bImmediateFail ){ |
| 332 | fossil_fatal("%s", p->zErr); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | /* |
| 337 | ** Free an email sender object |
| 338 | */ |
| 339 | void email_sender_free(EmailSender *p){ |
| 340 | emailerShutdown(p); |
| 341 | fossil_free(p->zErr); |
| 342 | fossil_free(p); |
| 343 | } |
| 344 | |
| 345 | /* |
| 346 | ** Get an email setting value. Report an error if not configured. |
| 347 | ** Return 0 on success and one if there is an error. |
| 348 | */ |
| 349 | static int emailerGetSetting( |
| 350 | EmailSender *p, /* Where to report the error */ |
| 351 | const char **pzVal, /* Write the setting value here */ |
| 352 | const char *zName /* Name of the setting */ |
| 353 | ){ |
| 354 | const char *z = db_get(zName, 0); |
| 355 | int rc = 0; |
| 356 | if( z==0 || z[0]==0 ){ |
| 357 | emailerError(p, "missing \"%s\" setting", zName); |
| 358 | rc = 1; |
| 359 | }else{ |
| 360 | *pzVal = z; |
| 361 | } |
| 362 | return rc; |
| 363 | } |
| 364 | |
| 365 | /* |
| 366 | ** Create a new EmailSender object. |
| 367 | ** |
| 368 | ** The method used for sending email is determined by various email-* |
| 369 | ** settings, and especially email-send-method. The repository |
| 370 | ** email-send-method can be overridden by the zAltDest argument to |
| 371 | ** cause a different sending mechanism to be used. Pass "stdout" to |
| 372 | ** zAltDest to cause all emails to be printed to the console for |
| 373 | ** debugging purposes. |
| 374 | ** |
| 375 | ** The EmailSender object returned must be freed using email_sender_free(). |
| 376 | */ |
| 377 | EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){ |
| 378 | EmailSender *p; |
| 379 | char *zMissing = 0; |
| 380 | |
| 381 | p = fossil_malloc(sizeof(*p)); |
| 382 | memset(p, 0, sizeof(*p)); |
| 383 | p->bImmediateFail = bImmediateFail; |
| 384 | if( zAltDest ){ |
| 385 | p->zDest = zAltDest; |
| 386 | }else{ |
| 387 | p->zDest = db_get("email-send-method","off"); |
| 388 | } |
| 389 | if( fossil_strcmp(p->zDest,"off")==0 ) return p; |
| 390 | if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p; |
| 391 | if( fossil_strcmp(p->zDest,"db")==0 ){ |
| 392 | char *zErr; |
| 393 | int rc; |
| 394 | if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p; |
| 395 | rc = sqlite3_open(p->zDb, &p->db); |
| 396 | if( rc ){ |
| 397 | emailerError(p, "unable to open output database file \"%s\": %s", |
| 398 | p->zDb, sqlite3_errmsg(p->db)); |
| 399 | return p; |
| 400 | } |
| 401 | rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n" |
| 402 | " emailid INTEGER PRIMARY KEY,\n" |
| 403 | " msg TEXT\n);", 0, 0, &zErr); |
| 404 | if( zErr ){ |
| 405 | emailerError(p, "CREATE TABLE failed with \"%s\"", zErr); |
| 406 | sqlite3_free(zErr); |
| 407 | return p; |
| 408 | } |
| 409 | rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1, |
| 410 | &p->pStmt, 0); |
| 411 | if( rc ){ |
| 412 | emailerError(p, "cannot prepare INSERT statement: %s", |
| 413 | sqlite3_errmsg(p->db)); |
| 414 | return p; |
| 415 | } |
| 416 | }else if( fossil_strcmp(p->zDest, "pipe")==0 ){ |
| 417 | emailerGetSetting(p, &p->zCmd, "email-send-command"); |
| 418 | }else if( fossil_strcmp(p->zDest, "dir")==0 ){ |
| 419 | emailerGetSetting(p, &p->zDir, "email-send-dir"); |
| 420 | } |
| 421 | return p; |
| 422 | } |
| 423 | |
| 424 | /* |
| 425 | ** Send a single email message. |
| 426 | ** |
| 427 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| 428 | ** in the header. Likewise, the header must contains a "Subject:" line. |
| 429 | ** The header might also include fields like "Message-Id:" or |
| 430 | ** "In-Reply-To:". |
| @@ -305,97 +433,50 @@ | |
| 433 | ** |
| 434 | ** From: |
| 435 | ** Content-Type: |
| 436 | ** Content-Transfer-Encoding: |
| 437 | ** |
| 438 | ** The caller maintains ownership of the input Blobs. This routine will |
| 439 | ** read the Blobs and send them onward to the email system, but it will |
| 440 | ** not free them. |
| 441 | */ |
| 442 | void email_send(EmailSender *p, Blob *pHdr, Blob *pBody){ |
| 443 | Blob all; |
| 444 | if( fossil_strcmp(p->zDest, "off")==0 ){ |
| 445 | return; |
| 446 | } |
| 447 | blob_init(&all, 0, 0); |
| 448 | blob_append(&all, blob_buffer(pHdr), blob_size(pHdr)); |
| 449 | blob_appendf(&all, "From: %s\r\n", p->zFrom); |
| 450 | blob_add_final_newline(pBody); |
| 451 | blob_appendf(&all,"Content-Type: text/plain\r\n"); |
| 452 | blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n"); |
| 453 | append_base64(&all, pBody); |
| 454 | if( p->pStmt ){ |
| 455 | int i, rc; |
| 456 | sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); |
| 457 | for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){ |
| 458 | sqlite3_sleep(10); |
| 459 | } |
| 460 | rc = sqlite3_reset(p->pStmt); |
| 461 | if( rc!=SQLITE_OK ){ |
| 462 | emailerError(p, "Failed to insert email message into output queue.\n" |
| 463 | "%s", sqlite3_errmsg(p->db)); |
| 464 | } |
| 465 | }else if( p->zCmd ){ |
| 466 | FILE *out = popen(p->zCmd, "w"); |
| 467 | if( out ){ |
| 468 | fwrite(blob_buffer(&all), 1, blob_size(&all), out); |
| 469 | fclose(out); |
| 470 | }else{ |
| 471 | emailerError(p, "Could not open output pipe \"%s\"", p->zCmd); |
| 472 | } |
| 473 | }else if( p->zDir ){ |
| 474 | char *zFile = emailTempFilename(p->zDir); |
| 475 | blob_write_to_file(&all, zFile); |
| 476 | fossil_free(zFile); |
| 477 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 478 | fossil_print("%s\n", blob_str(&all)); |
| 479 | } |
| 480 | blob_zero(&all); |
| 481 | } |
| 482 | |
| @@ -483,11 +564,10 @@ | |
| 564 | ** email sending mechanism is currently configured. |
| 565 | ** Use this for testing the email configuration. |
| 566 | ** Options: |
| 567 | ** |
| 568 | ** --body FILENAME |
| 569 | ** --stdout |
| 570 | ** --subject|-S SUBJECT |
| 571 | ** |
| 572 | ** settings [NAME VALUE] With no arguments, list all email settings. |
| 573 | ** Or change the value of a single email setting. |
| @@ -556,16 +636,15 @@ | |
| 636 | email_schema(); |
| 637 | } |
| 638 | }else |
| 639 | if( strncmp(zCmd, "send", nCmd)==0 ){ |
| 640 | Blob prompt, body, hdr; |
| 641 | const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; |
| 642 | int i; |
| 643 | const char *zSubject = find_option("subject", "S", 1); |
| 644 | const char *zSource = find_option("body", 0, 1); |
| 645 | EmailSender *pSender; |
| 646 | verify_all_options(); |
| 647 | blob_init(&prompt, 0, 0); |
| 648 | blob_init(&body, 0, 0); |
| 649 | blob_init(&hdr, 0, 0); |
| 650 | for(i=3; i<g.argc; i++){ |
| @@ -578,21 +657,13 @@ | |
| 657 | blob_read_from_file(&body, zSource, ExtFILE); |
| 658 | }else{ |
| 659 | prompt_for_user_comment(&body, &prompt); |
| 660 | } |
| 661 | blob_add_final_newline(&body); |
| 662 | pSender = email_sender_new(zDest, 1); |
| 663 | email_send(pSender, &hdr, &body); |
| 664 | email_sender_free(pSender); |
| 665 | blob_zero(&hdr); |
| 666 | blob_zero(&body); |
| 667 | blob_zero(&prompt); |
| 668 | }else |
| 669 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| @@ -822,21 +893,32 @@ | |
| 893 | cgi_redirectf("%R/alerts/%s", zCode); |
| 894 | return; |
| 895 | }else{ |
| 896 | /* We need to send a verification email */ |
| 897 | Blob hdr, body; |
| 898 | EmailSender *pSender = email_sender_new(0,0); |
| 899 | blob_init(&hdr,0,0); |
| 900 | blob_init(&body,0,0); |
| 901 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 902 | blob_appendf(&hdr, "Subject: Subscription verification\n"); |
| 903 | blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/, |
| 904 | g.zBaseURL, g.zBaseURL, zCode); |
| 905 | email_send(pSender, &hdr, &body); |
| 906 | style_header("Email Alert Verification"); |
| 907 | if( pSender->zErr ){ |
| 908 | @ <h1>Internal Error</h1> |
| 909 | @ <p>The following internal error was encountered while trying |
| 910 | @ to send the confirmation email: |
| 911 | @ <blockquote><pre> |
| 912 | @ %h(pSender->zErr) |
| 913 | @ </pre></blockquote> |
| 914 | }else{ |
| 915 | @ <p>An email has been sent to "%h(zEAddr)". That email contains a |
| 916 | @ hyperlink that you must click on in order to activate your |
| 917 | @ subscription.</p> |
| 918 | } |
| 919 | email_sender_free(pSender); |
| 920 | style_footer(); |
| 921 | } |
| 922 | return; |
| 923 | } |
| 924 | style_header("Signup For Email Alerts"); |
| @@ -1213,20 +1295,31 @@ | |
| 1295 | } |
| 1296 | if( bSubmit ){ |
| 1297 | /* If we get this far, it means that a valid unsubscribe request has |
| 1298 | ** been submitted. Send the appropriate email. */ |
| 1299 | Blob hdr, body; |
| 1300 | EmailSender *pSender = email_sender_new(0,0); |
| 1301 | blob_init(&hdr,0,0); |
| 1302 | blob_init(&body,0,0); |
| 1303 | blob_appendf(&hdr, "To: %s\n", zEAddr); |
| 1304 | blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n"); |
| 1305 | blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/, |
| 1306 | g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode); |
| 1307 | email_send(pSender, &hdr, &body); |
| 1308 | style_header("Unsubscribe Instructions Sent"); |
| 1309 | if( pSender->zErr ){ |
| 1310 | @ <h1>Internal Error</h1> |
| 1311 | @ <p>The following error was encountered while trying to send an |
| 1312 | @ email to %h(zEAddr): |
| 1313 | @ <blockquote><pre> |
| 1314 | @ %h(pSender->zErr) |
| 1315 | @ </pre></blockquote> |
| 1316 | }else{ |
| 1317 | @ <p>An email has been sent to "%h(zEAddr)" that explains how to |
| 1318 | @ unsubscribe and/or modify your subscription settings</p> |
| 1319 | } |
| 1320 | email_sender_free(pSender); |
| 1321 | style_footer(); |
| 1322 | return; |
| 1323 | } |
| 1324 | |
| 1325 | /* Non-logged-in users have to enter an email address to which is |
| @@ -1525,19 +1618,21 @@ | |
| 1618 | Blob hdr, body; |
| 1619 | const char *zUrl; |
| 1620 | const char *zRepoName; |
| 1621 | const char *zFrom; |
| 1622 | const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; |
| 1623 | EmailSender *pSender = 0; |
| 1624 | |
| 1625 | db_begin_transaction(); |
| 1626 | if( !email_enabled() ) goto send_alerts_done; |
| 1627 | zUrl = db_get("email-url",0); |
| 1628 | if( zUrl==0 ) goto send_alerts_done; |
| 1629 | zRepoName = db_get("email-subname",0); |
| 1630 | if( zRepoName==0 ) goto send_alerts_done; |
| 1631 | zFrom = db_get("email-self",0); |
| 1632 | if( zFrom==0 ) goto send_alerts_done; |
| 1633 | pSender = email_sender_new(zDest, 0); |
| 1634 | db_multi_exec( |
| 1635 | "DROP TABLE IF EXISTS temp.wantalert;" |
| 1636 | "CREATE TEMP TABLE wantalert(eventId TEXT);" |
| 1637 | ); |
| 1638 | if( flags & SENDALERT_DIGEST ){ |
| @@ -1587,11 +1682,11 @@ | |
| 1682 | blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1683 | } |
| 1684 | if( nHit==0 ) continue; |
| 1685 | blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n", |
| 1686 | '-', zUrl, zCode); |
| 1687 | email_send(pSender,&hdr,&body); |
| 1688 | blob_truncate(&hdr); |
| 1689 | blob_truncate(&body); |
| 1690 | } |
| 1691 | blob_zero(&hdr); |
| 1692 | blob_zero(&body); |
| @@ -1604,7 +1699,8 @@ | |
| 1699 | db_multi_exec("UPDATE pending_alert SET sentSep=true"); |
| 1700 | } |
| 1701 | db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep"); |
| 1702 | } |
| 1703 | send_alerts_done: |
| 1704 | email_sender_free(pSender); |
| 1705 | db_end_transaction(0); |
| 1706 | } |
| 1707 |