Fossil SCM

Change the email transfer encoding to quoted-printable.

drh 2018-06-26 02:01 trunk
Commit b6a13c45bd0e59cc584c8f8408dd53bc3690d7225b6f7cff42042fcc29126717
2 files changed +19 -6 +50 -7
+19 -6
--- src/blob.c
+++ src/blob.c
@@ -293,10 +293,23 @@
293293
}
294294
memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
295295
pBlob->nUsed += nData;
296296
pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */
297297
}
298
+
299
+/*
300
+** Append a single character to the blob
301
+*/
302
+void blob_append_char(Blob *pBlob, char c){
303
+ if( pBlob->nUsed+1>pBlob->nAlloc ){
304
+ pBlob->xRealloc(pBlob, pBlob->nUsed + pBlob->nAlloc + 100);
305
+ if( pBlob->nUsed + 1 >= pBlob->nAlloc ){
306
+ blob_panic();
307
+ }
308
+ }
309
+ pBlob->aData[pBlob->nUsed++] = c;
310
+}
298311
299312
/*
300313
** Copy a blob
301314
*/
302315
void blob_copy(Blob *pTo, Blob *pFrom){
@@ -309,11 +322,11 @@
309322
** Return a pointer to a null-terminated string for a blob.
310323
*/
311324
char *blob_str(Blob *p){
312325
blob_is_init(p);
313326
if( p->nUsed==0 ){
314
- blob_append(p, "", 1); /* NOTE: Changes nUsed. */
327
+ blob_append_char(p, 0); /* NOTE: Changes nUsed. */
315328
p->nUsed = 0;
316329
}
317330
if( p->aData[p->nUsed]!=0 ){
318331
blob_materialize(p);
319332
}
@@ -667,11 +680,11 @@
667680
** Ensure that the text in pBlob ends with '\n'
668681
*/
669682
void blob_add_final_newline(Blob *pBlob){
670683
if( pBlob->nUsed<=0 ) return;
671684
if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
672
- blob_append(pBlob, "\n", 1);
685
+ blob_append_char(pBlob, '\n');
673686
}
674687
}
675688
676689
/*
677690
** Return true if the blob contains a valid base16 identifier artifact hash.
@@ -1254,16 +1267,16 @@
12541267
if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){
12551268
needEscape = 1;
12561269
}
12571270
}
12581271
if( n>0 && !fossil_isspace(z[n-1]) ){
1259
- blob_append(pBlob, " ", 1);
1272
+ blob_append_char(pBlob, ' ');
12601273
}
1261
- if( needEscape ) blob_append(pBlob, &cQuote, 1);
1274
+ if( needEscape ) blob_append_char(pBlob, cQuote);
12621275
if( zIn[0]=='-' ) blob_append(pBlob, "./", 2);
12631276
blob_append(pBlob, zIn, -1);
1264
- if( needEscape ) blob_append(pBlob, &cQuote, 1);
1277
+ if( needEscape ) blob_append_char(pBlob, cQuote);
12651278
}
12661279
12671280
/*
12681281
** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
12691282
** bytes from pIn, starting at position pIn->iCursor, and copies them
@@ -1330,11 +1343,11 @@
13301343
zUtf8[i] = zUtf8[i-1];
13311344
zUtf8[--i] = zTemp;
13321345
}
13331346
}
13341347
/* Make sure the blob contains two terminating 0-bytes */
1335
- blob_append(pBlob, "", 1);
1348
+ blob_append_char(pBlob, 0);
13361349
zUtf8 = blob_str(pBlob) + bomSize;
13371350
zUtf8 = fossil_unicode_to_utf8(zUtf8);
13381351
blob_set_dynamic(pBlob, zUtf8);
13391352
}else if( useMbcs && invalid_utf8(pBlob) ){
13401353
#if defined(_WIN32) || defined(__CYGWIN__)
13411354
--- src/blob.c
+++ src/blob.c
@@ -293,10 +293,23 @@
293 }
294 memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
295 pBlob->nUsed += nData;
296 pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */
297 }
 
 
 
 
 
 
 
 
 
 
 
 
 
298
299 /*
300 ** Copy a blob
301 */
302 void blob_copy(Blob *pTo, Blob *pFrom){
@@ -309,11 +322,11 @@
309 ** Return a pointer to a null-terminated string for a blob.
310 */
311 char *blob_str(Blob *p){
312 blob_is_init(p);
313 if( p->nUsed==0 ){
314 blob_append(p, "", 1); /* NOTE: Changes nUsed. */
315 p->nUsed = 0;
316 }
317 if( p->aData[p->nUsed]!=0 ){
318 blob_materialize(p);
319 }
@@ -667,11 +680,11 @@
667 ** Ensure that the text in pBlob ends with '\n'
668 */
669 void blob_add_final_newline(Blob *pBlob){
670 if( pBlob->nUsed<=0 ) return;
671 if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
672 blob_append(pBlob, "\n", 1);
673 }
674 }
675
676 /*
677 ** Return true if the blob contains a valid base16 identifier artifact hash.
@@ -1254,16 +1267,16 @@
1254 if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){
1255 needEscape = 1;
1256 }
1257 }
1258 if( n>0 && !fossil_isspace(z[n-1]) ){
1259 blob_append(pBlob, " ", 1);
1260 }
1261 if( needEscape ) blob_append(pBlob, &cQuote, 1);
1262 if( zIn[0]=='-' ) blob_append(pBlob, "./", 2);
1263 blob_append(pBlob, zIn, -1);
1264 if( needEscape ) blob_append(pBlob, &cQuote, 1);
1265 }
1266
1267 /*
1268 ** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
1269 ** bytes from pIn, starting at position pIn->iCursor, and copies them
@@ -1330,11 +1343,11 @@
1330 zUtf8[i] = zUtf8[i-1];
1331 zUtf8[--i] = zTemp;
1332 }
1333 }
1334 /* Make sure the blob contains two terminating 0-bytes */
1335 blob_append(pBlob, "", 1);
1336 zUtf8 = blob_str(pBlob) + bomSize;
1337 zUtf8 = fossil_unicode_to_utf8(zUtf8);
1338 blob_set_dynamic(pBlob, zUtf8);
1339 }else if( useMbcs && invalid_utf8(pBlob) ){
1340 #if defined(_WIN32) || defined(__CYGWIN__)
1341
--- src/blob.c
+++ src/blob.c
@@ -293,10 +293,23 @@
293 }
294 memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
295 pBlob->nUsed += nData;
296 pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */
297 }
298
299 /*
300 ** Append a single character to the blob
301 */
302 void blob_append_char(Blob *pBlob, char c){
303 if( pBlob->nUsed+1>pBlob->nAlloc ){
304 pBlob->xRealloc(pBlob, pBlob->nUsed + pBlob->nAlloc + 100);
305 if( pBlob->nUsed + 1 >= pBlob->nAlloc ){
306 blob_panic();
307 }
308 }
309 pBlob->aData[pBlob->nUsed++] = c;
310 }
311
312 /*
313 ** Copy a blob
314 */
315 void blob_copy(Blob *pTo, Blob *pFrom){
@@ -309,11 +322,11 @@
322 ** Return a pointer to a null-terminated string for a blob.
323 */
324 char *blob_str(Blob *p){
325 blob_is_init(p);
326 if( p->nUsed==0 ){
327 blob_append_char(p, 0); /* NOTE: Changes nUsed. */
328 p->nUsed = 0;
329 }
330 if( p->aData[p->nUsed]!=0 ){
331 blob_materialize(p);
332 }
@@ -667,11 +680,11 @@
680 ** Ensure that the text in pBlob ends with '\n'
681 */
682 void blob_add_final_newline(Blob *pBlob){
683 if( pBlob->nUsed<=0 ) return;
684 if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
685 blob_append_char(pBlob, '\n');
686 }
687 }
688
689 /*
690 ** Return true if the blob contains a valid base16 identifier artifact hash.
@@ -1254,16 +1267,16 @@
1267 if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){
1268 needEscape = 1;
1269 }
1270 }
1271 if( n>0 && !fossil_isspace(z[n-1]) ){
1272 blob_append_char(pBlob, ' ');
1273 }
1274 if( needEscape ) blob_append_char(pBlob, cQuote);
1275 if( zIn[0]=='-' ) blob_append(pBlob, "./", 2);
1276 blob_append(pBlob, zIn, -1);
1277 if( needEscape ) blob_append_char(pBlob, cQuote);
1278 }
1279
1280 /*
1281 ** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
1282 ** bytes from pIn, starting at position pIn->iCursor, and copies them
@@ -1330,11 +1343,11 @@
1343 zUtf8[i] = zUtf8[i-1];
1344 zUtf8[--i] = zTemp;
1345 }
1346 }
1347 /* Make sure the blob contains two terminating 0-bytes */
1348 blob_append_char(pBlob, 0);
1349 zUtf8 = blob_str(pBlob) + bomSize;
1350 zUtf8 = fossil_unicode_to_utf8(zUtf8);
1351 blob_set_dynamic(pBlob, zUtf8);
1352 }else if( useMbcs && invalid_utf8(pBlob) ){
1353 #if defined(_WIN32) || defined(__CYGWIN__)
1354
+50 -7
--- src/email.c
+++ src/email.c
@@ -291,10 +291,11 @@
291291
@ </div></form>
292292
db_end_transaction(0);
293293
style_footer();
294294
}
295295
296
+#if 0
296297
/*
297298
** Encode pMsg as MIME base64 and append it to pOut
298299
*/
299300
static void append_base64(Blob *pOut, Blob *pMsg){
300301
int n, i, k;
@@ -303,10 +304,47 @@
303304
for(i=0; i<n; i+=54){
304305
k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf);
305306
blob_append(pOut, zBuf, k);
306307
blob_append(pOut, "\r\n", 2);
307308
}
309
+}
310
+#endif
311
+
312
+/*
313
+** Encode pMsg using the quoted-printable email encoding and
314
+** append it onto pOut
315
+*/
316
+static void append_quoted(Blob *pOut, Blob *pMsg){
317
+ char *zIn = blob_str(pMsg);
318
+ char c;
319
+ int iCol = 0;
320
+ while( (c = *(zIn++))!=0 ){
321
+ if( (c>='!' && c<='~' && c!='=')
322
+ || (c==' ' && zIn[0]!='\r' && zIn[0]!='\n')
323
+ ){
324
+ blob_append_char(pOut, c);
325
+ iCol++;
326
+ if( iCol>=70 ){
327
+ blob_append(pOut, "=\n", 2);
328
+ iCol = 0;
329
+ }
330
+ }else if( c=='\r' && zIn[0]=='\n' ){
331
+ zIn++;
332
+ blob_append(pOut, "\r\n", 2);
333
+ iCol = 0;
334
+ }else if( c=='\n' ){
335
+ blob_append(pOut, "\r\n", 2);
336
+ iCol = 0;
337
+ }else{
338
+ char x[3];
339
+ x[0] = '=';
340
+ x[1] = "0123456789ABCDEF"[(c>>4)&0xf];
341
+ x[2] = "0123456789ABCDEF"[c&0xf];
342
+ blob_append(pOut, x, 3);
343
+ iCol += 3;
344
+ }
345
+ }
308346
}
309347
310348
/*
311349
** Come up with a unique filename in the zDir directory.
312350
**
@@ -498,12 +536,17 @@
498536
}
499537
blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
500538
blob_appendf(pOut, "From: %s\r\n", p->zFrom);
501539
blob_add_final_newline(pBody);
502540
blob_appendf(pOut,"Content-Type: text/plain\r\n");
541
+#if 0
503542
blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
504543
append_base64(pOut, pBody);
544
+#else
545
+ blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
546
+ append_quoted(pOut, pBody);
547
+#endif
505548
if( p->pStmt ){
506549
int i, rc;
507550
sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
508551
for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
509552
sqlite3_sleep(10);
@@ -1610,12 +1653,12 @@
16101653
/*
16111654
** Append the "unsubscribe" notification and other footer text to
16121655
** the end of an email alert being assemblied in pOut.
16131656
*/
16141657
void email_footer(Blob *pOut){
1615
- blob_appendf(pOut, "\n%.72c\nTo unsubscribe: %s/unsubscribe\n",
1616
- '-', db_get("email-url","http://localhost:8080"));
1658
+ blob_appendf(pOut, "\n-- \nTo unsubscribe: %s/unsubscribe\n",
1659
+ db_get("email-url","http://localhost:8080"));
16171660
}
16181661
16191662
/*
16201663
** COMMAND: test-alert
16211664
**
@@ -1771,12 +1814,12 @@
17711814
nHit++;
17721815
blob_append(&body, "\n", 1);
17731816
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
17741817
}
17751818
if( nHit==0 ) continue;
1776
- blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1777
- '-', zUrl, zCode);
1819
+ blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
1820
+ zUrl, zCode);
17781821
email_send(pSender,&hdr,&body);
17791822
blob_truncate(&hdr, 0);
17801823
blob_truncate(&body, 0);
17811824
}
17821825
blob_zero(&hdr);
@@ -1949,22 +1992,22 @@
19491992
}
19501993
if( bAll || bAA ){
19511994
Stmt q;
19521995
int nUsed = blob_size(&body);
19531996
const char *zURL = db_get("email-url",0);
1954
- db_prepare(&q, "SELECT semail, subscriberCode FROM subscriber "
1997
+ db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
19551998
" WHERE sverified AND NOT sdonotcall %s",
19561999
bAll ? "" : " AND ssub LIKE '%a%'");
19572000
while( db_step(&q)==SQLITE_ROW ){
19582001
const char *zCode = db_column_text(&q, 1);
19592002
zTo = db_column_text(&q, 0);
19602003
blob_truncate(&hdr, 0);
19612004
blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
19622005
if( zURL ){
19632006
blob_truncate(&body, nUsed);
1964
- blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1965
- '-', zURL, zCode);
2007
+ blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
2008
+ zURL, zCode);
19662009
}
19672010
email_send(pSender, &hdr, &body);
19682011
}
19692012
db_finalize(&q);
19702013
}
19712014
--- src/email.c
+++ src/email.c
@@ -291,10 +291,11 @@
291 @ </div></form>
292 db_end_transaction(0);
293 style_footer();
294 }
295
 
296 /*
297 ** Encode pMsg as MIME base64 and append it to pOut
298 */
299 static void append_base64(Blob *pOut, Blob *pMsg){
300 int n, i, k;
@@ -303,10 +304,47 @@
303 for(i=0; i<n; i+=54){
304 k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf);
305 blob_append(pOut, zBuf, k);
306 blob_append(pOut, "\r\n", 2);
307 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308 }
309
310 /*
311 ** Come up with a unique filename in the zDir directory.
312 **
@@ -498,12 +536,17 @@
498 }
499 blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
500 blob_appendf(pOut, "From: %s\r\n", p->zFrom);
501 blob_add_final_newline(pBody);
502 blob_appendf(pOut,"Content-Type: text/plain\r\n");
 
503 blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
504 append_base64(pOut, pBody);
 
 
 
 
505 if( p->pStmt ){
506 int i, rc;
507 sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
508 for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
509 sqlite3_sleep(10);
@@ -1610,12 +1653,12 @@
1610 /*
1611 ** Append the "unsubscribe" notification and other footer text to
1612 ** the end of an email alert being assemblied in pOut.
1613 */
1614 void email_footer(Blob *pOut){
1615 blob_appendf(pOut, "\n%.72c\nTo unsubscribe: %s/unsubscribe\n",
1616 '-', db_get("email-url","http://localhost:8080"));
1617 }
1618
1619 /*
1620 ** COMMAND: test-alert
1621 **
@@ -1771,12 +1814,12 @@
1771 nHit++;
1772 blob_append(&body, "\n", 1);
1773 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
1774 }
1775 if( nHit==0 ) continue;
1776 blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1777 '-', zUrl, zCode);
1778 email_send(pSender,&hdr,&body);
1779 blob_truncate(&hdr, 0);
1780 blob_truncate(&body, 0);
1781 }
1782 blob_zero(&hdr);
@@ -1949,22 +1992,22 @@
1949 }
1950 if( bAll || bAA ){
1951 Stmt q;
1952 int nUsed = blob_size(&body);
1953 const char *zURL = db_get("email-url",0);
1954 db_prepare(&q, "SELECT semail, subscriberCode FROM subscriber "
1955 " WHERE sverified AND NOT sdonotcall %s",
1956 bAll ? "" : " AND ssub LIKE '%a%'");
1957 while( db_step(&q)==SQLITE_ROW ){
1958 const char *zCode = db_column_text(&q, 1);
1959 zTo = db_column_text(&q, 0);
1960 blob_truncate(&hdr, 0);
1961 blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
1962 if( zURL ){
1963 blob_truncate(&body, nUsed);
1964 blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1965 '-', zURL, zCode);
1966 }
1967 email_send(pSender, &hdr, &body);
1968 }
1969 db_finalize(&q);
1970 }
1971
--- src/email.c
+++ src/email.c
@@ -291,10 +291,11 @@
291 @ </div></form>
292 db_end_transaction(0);
293 style_footer();
294 }
295
296 #if 0
297 /*
298 ** Encode pMsg as MIME base64 and append it to pOut
299 */
300 static void append_base64(Blob *pOut, Blob *pMsg){
301 int n, i, k;
@@ -303,10 +304,47 @@
304 for(i=0; i<n; i+=54){
305 k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf);
306 blob_append(pOut, zBuf, k);
307 blob_append(pOut, "\r\n", 2);
308 }
309 }
310 #endif
311
312 /*
313 ** Encode pMsg using the quoted-printable email encoding and
314 ** append it onto pOut
315 */
316 static void append_quoted(Blob *pOut, Blob *pMsg){
317 char *zIn = blob_str(pMsg);
318 char c;
319 int iCol = 0;
320 while( (c = *(zIn++))!=0 ){
321 if( (c>='!' && c<='~' && c!='=')
322 || (c==' ' && zIn[0]!='\r' && zIn[0]!='\n')
323 ){
324 blob_append_char(pOut, c);
325 iCol++;
326 if( iCol>=70 ){
327 blob_append(pOut, "=\n", 2);
328 iCol = 0;
329 }
330 }else if( c=='\r' && zIn[0]=='\n' ){
331 zIn++;
332 blob_append(pOut, "\r\n", 2);
333 iCol = 0;
334 }else if( c=='\n' ){
335 blob_append(pOut, "\r\n", 2);
336 iCol = 0;
337 }else{
338 char x[3];
339 x[0] = '=';
340 x[1] = "0123456789ABCDEF"[(c>>4)&0xf];
341 x[2] = "0123456789ABCDEF"[c&0xf];
342 blob_append(pOut, x, 3);
343 iCol += 3;
344 }
345 }
346 }
347
348 /*
349 ** Come up with a unique filename in the zDir directory.
350 **
@@ -498,12 +536,17 @@
536 }
537 blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
538 blob_appendf(pOut, "From: %s\r\n", p->zFrom);
539 blob_add_final_newline(pBody);
540 blob_appendf(pOut,"Content-Type: text/plain\r\n");
541 #if 0
542 blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
543 append_base64(pOut, pBody);
544 #else
545 blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
546 append_quoted(pOut, pBody);
547 #endif
548 if( p->pStmt ){
549 int i, rc;
550 sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
551 for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
552 sqlite3_sleep(10);
@@ -1610,12 +1653,12 @@
1653 /*
1654 ** Append the "unsubscribe" notification and other footer text to
1655 ** the end of an email alert being assemblied in pOut.
1656 */
1657 void email_footer(Blob *pOut){
1658 blob_appendf(pOut, "\n-- \nTo unsubscribe: %s/unsubscribe\n",
1659 db_get("email-url","http://localhost:8080"));
1660 }
1661
1662 /*
1663 ** COMMAND: test-alert
1664 **
@@ -1771,12 +1814,12 @@
1814 nHit++;
1815 blob_append(&body, "\n", 1);
1816 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
1817 }
1818 if( nHit==0 ) continue;
1819 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
1820 zUrl, zCode);
1821 email_send(pSender,&hdr,&body);
1822 blob_truncate(&hdr, 0);
1823 blob_truncate(&body, 0);
1824 }
1825 blob_zero(&hdr);
@@ -1949,22 +1992,22 @@
1992 }
1993 if( bAll || bAA ){
1994 Stmt q;
1995 int nUsed = blob_size(&body);
1996 const char *zURL = db_get("email-url",0);
1997 db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
1998 " WHERE sverified AND NOT sdonotcall %s",
1999 bAll ? "" : " AND ssub LIKE '%a%'");
2000 while( db_step(&q)==SQLITE_ROW ){
2001 const char *zCode = db_column_text(&q, 1);
2002 zTo = db_column_text(&q, 0);
2003 blob_truncate(&hdr, 0);
2004 blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
2005 if( zURL ){
2006 blob_truncate(&body, nUsed);
2007 blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
2008 zURL, zCode);
2009 }
2010 email_send(pSender, &hdr, &body);
2011 }
2012 db_finalize(&q);
2013 }
2014

Keyboard Shortcuts

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