Fossil

Check-in [b6a13c45]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Change the email transfer encoding to quoted-printable.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: b6a13c45bd0e59cc584c8f8408dd53bc3690d7225b6f7cff42042fcc29126717
User & Date: drh 2018-06-26 02:01:40
Context
2018-06-26
02:26
Fix a critical bug in the new blob_append_char() routine that was added to the previous check-in. This bug causes crashes. Also change the quoted-printable encoding to escape the ':' character so that lines in the body of an email will never be confused with a header line like "From:". check-in: 122905c2 user: drh tags: trunk
02:01
Change the email transfer encoding to quoted-printable. check-in: b6a13c45 user: drh tags: trunk
01:24
Initial implementation of the /announce webpage. check-in: 65f57546 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/blob.c.

291
292
293
294
295
296
297













298
299
300
301
302
303
304
...
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
...
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
....
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
....
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
      blob_panic();
    }
  }
  memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
  pBlob->nUsed += nData;
  pBlob->aData[pBlob->nUsed] = 0;   /* Blobs are always nul-terminated */
}














/*
** Copy a blob
*/
void blob_copy(Blob *pTo, Blob *pFrom){
  blob_is_init(pFrom);
  blob_zero(pTo);
................................................................................

/*
** Return a pointer to a null-terminated string for a blob.
*/
char *blob_str(Blob *p){
  blob_is_init(p);
  if( p->nUsed==0 ){
    blob_append(p, "", 1); /* NOTE: Changes nUsed. */
    p->nUsed = 0;
  }
  if( p->aData[p->nUsed]!=0 ){
    blob_materialize(p);
  }
  return p->aData;
}
................................................................................

/*
** Ensure that the text in pBlob ends with '\n'
*/
void blob_add_final_newline(Blob *pBlob){
  if( pBlob->nUsed<=0 ) return;
  if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
    blob_append(pBlob, "\n", 1);
  }
}

/*
** Return true if the blob contains a valid base16 identifier artifact hash.
**
** The value returned is actually one of HNAME_SHA1 OR HNAME_K256 if the
................................................................................
                   zIn, blob_str(&bad), c);
    }
    if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){
      needEscape = 1;
    }
  }
  if( n>0 && !fossil_isspace(z[n-1]) ){
    blob_append(pBlob, " ", 1);
  }
  if( needEscape ) blob_append(pBlob, &cQuote, 1);
  if( zIn[0]=='-' ) blob_append(pBlob, "./", 2);
  blob_append(pBlob, zIn, -1);
  if( needEscape ) blob_append(pBlob, &cQuote, 1);
}

/*
** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
** bytes from pIn, starting at position pIn->iCursor, and copies them
** to pDest (which must be valid memory at least nLen bytes long).
**
................................................................................
        /* swap bytes of unicode representation */
        char zTemp = zUtf8[--i];
        zUtf8[i] = zUtf8[i-1];
        zUtf8[--i] = zTemp;
      }
    }
    /* Make sure the blob contains two terminating 0-bytes */
    blob_append(pBlob, "", 1);
    zUtf8 = blob_str(pBlob) + bomSize;
    zUtf8 = fossil_unicode_to_utf8(zUtf8);
    blob_set_dynamic(pBlob, zUtf8);
  }else if( useMbcs && invalid_utf8(pBlob) ){
#if defined(_WIN32) || defined(__CYGWIN__)
    zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
    blob_reset(pBlob);







>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|







 







|

|


|







 







|







291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
...
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
...
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
....
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
....
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
      blob_panic();
    }
  }
  memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
  pBlob->nUsed += nData;
  pBlob->aData[pBlob->nUsed] = 0;   /* Blobs are always nul-terminated */
}

/*
** Append a single character to the blob
*/
void blob_append_char(Blob *pBlob, char c){
  if( pBlob->nUsed+1>pBlob->nAlloc ){
    pBlob->xRealloc(pBlob, pBlob->nUsed + pBlob->nAlloc + 100);
    if( pBlob->nUsed + 1 >= pBlob->nAlloc ){
      blob_panic();
    }
  }
  pBlob->aData[pBlob->nUsed++] = c;    
}

/*
** Copy a blob
*/
void blob_copy(Blob *pTo, Blob *pFrom){
  blob_is_init(pFrom);
  blob_zero(pTo);
................................................................................

/*
** Return a pointer to a null-terminated string for a blob.
*/
char *blob_str(Blob *p){
  blob_is_init(p);
  if( p->nUsed==0 ){
    blob_append_char(p, 0); /* NOTE: Changes nUsed. */
    p->nUsed = 0;
  }
  if( p->aData[p->nUsed]!=0 ){
    blob_materialize(p);
  }
  return p->aData;
}
................................................................................

/*
** Ensure that the text in pBlob ends with '\n'
*/
void blob_add_final_newline(Blob *pBlob){
  if( pBlob->nUsed<=0 ) return;
  if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
    blob_append_char(pBlob, '\n');
  }
}

/*
** Return true if the blob contains a valid base16 identifier artifact hash.
**
** The value returned is actually one of HNAME_SHA1 OR HNAME_K256 if the
................................................................................
                   zIn, blob_str(&bad), c);
    }
    if( !needEscape && !fossil_isalnum(c) && c!='/' && c!='.' && c!='_' ){
      needEscape = 1;
    }
  }
  if( n>0 && !fossil_isspace(z[n-1]) ){
    blob_append_char(pBlob, ' ');
  }
  if( needEscape ) blob_append_char(pBlob, cQuote);
  if( zIn[0]=='-' ) blob_append(pBlob, "./", 2);
  blob_append(pBlob, zIn, -1);
  if( needEscape ) blob_append_char(pBlob, cQuote);
}

/*
** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
** bytes from pIn, starting at position pIn->iCursor, and copies them
** to pDest (which must be valid memory at least nLen bytes long).
**
................................................................................
        /* swap bytes of unicode representation */
        char zTemp = zUtf8[--i];
        zUtf8[i] = zUtf8[i-1];
        zUtf8[--i] = zTemp;
      }
    }
    /* Make sure the blob contains two terminating 0-bytes */
    blob_append_char(pBlob, 0);
    zUtf8 = blob_str(pBlob) + bomSize;
    zUtf8 = fossil_unicode_to_utf8(zUtf8);
    blob_set_dynamic(pBlob, zUtf8);
  }else if( useMbcs && invalid_utf8(pBlob) ){
#if defined(_WIN32) || defined(__CYGWIN__)
    zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
    blob_reset(pBlob);

Changes to src/email.c.

289
290
291
292
293
294
295

296
297
298
299
300
301
302
303
304
305
306
307





































308
309
310
311
312
313
314
...
496
497
498
499
500
501
502

503
504




505
506
507
508
509
510
511
....
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
....
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
....
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}


/*
** Encode pMsg as MIME base64 and append it to pOut
*/
static void append_base64(Blob *pOut, Blob *pMsg){
  int n, i, k;
  char zBuf[100];
  n = blob_size(pMsg);
  for(i=0; i<n; i+=54){
    k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf);
    blob_append(pOut, zBuf, k);
    blob_append(pOut, "\r\n", 2);
  }





































}

/*
** Come up with a unique filename in the zDir directory.
**
** Space to hold the filename is obtained from mprintf() and must
** be freed using fossil_free() by the caller.
................................................................................
    blob_init(&all, 0, 0);
    pOut = &all;
  }
  blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
  blob_appendf(pOut, "From: %s\r\n", p->zFrom);
  blob_add_final_newline(pBody);
  blob_appendf(pOut,"Content-Type: text/plain\r\n");

  blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
  append_base64(pOut, pBody);




  if( p->pStmt ){
    int i, rc;
    sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
    for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
      sqlite3_sleep(10);
    }
    rc = sqlite3_reset(p->pStmt);
................................................................................
}

/*
** Append the "unsubscribe" notification and other footer text to
** the end of an email alert being assemblied in pOut.
*/
void email_footer(Blob *pOut){
  blob_appendf(pOut, "\n%.72c\nTo unsubscribe: %s/unsubscribe\n",
     '-', db_get("email-url","http://localhost:8080"));
}

/*
** COMMAND:  test-alert
**
** Usage: %fossil test-alert EVENTID ...
**
................................................................................
        );
      }
      nHit++;
      blob_append(&body, "\n", 1);
      blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
    }
    if( nHit==0 ) continue;
    blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
         '-', zUrl, zCode);
    email_send(pSender,&hdr,&body);
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_zero(&hdr);
  blob_zero(&body);
  db_finalize(&q);
................................................................................
    blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
    email_send(pSender, &hdr, &body);
  }
  if( bAll || bAA ){
    Stmt q;
    int nUsed = blob_size(&body);
    const char *zURL =  db_get("email-url",0);
    db_prepare(&q, "SELECT semail, subscriberCode FROM subscriber "
                   " WHERE sverified AND NOT sdonotcall %s",
                   bAll ? "" : " AND ssub LIKE '%a%'");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zCode = db_column_text(&q, 1);
      zTo = db_column_text(&q, 0);
      blob_truncate(&hdr, 0);
      blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
      if( zURL ){
        blob_truncate(&body, nUsed);
        blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
           '-', zURL, zCode);
      }
      email_send(pSender, &hdr, &body);
    }
    db_finalize(&q);
  }
  if( bTest2 ){
    /* If the URL is /announce/test2 instead of just /announce, then no







>












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>


>
>
>
>







 







|
|







 







|
|







 







|









|
|







289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
...
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
....
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
....
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
....
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes" /></p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
}

#if 0
/*
** Encode pMsg as MIME base64 and append it to pOut
*/
static void append_base64(Blob *pOut, Blob *pMsg){
  int n, i, k;
  char zBuf[100];
  n = blob_size(pMsg);
  for(i=0; i<n; i+=54){
    k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf);
    blob_append(pOut, zBuf, k);
    blob_append(pOut, "\r\n", 2);
  }
}
#endif

/*
** Encode pMsg using the quoted-printable email encoding and
** append it onto pOut
*/
static void append_quoted(Blob *pOut, Blob *pMsg){
  char *zIn = blob_str(pMsg);
  char c;
  int iCol = 0;
  while( (c = *(zIn++))!=0 ){
    if( (c>='!' && c<='~' && c!='=')
     || (c==' ' && zIn[0]!='\r' && zIn[0]!='\n')
    ){
      blob_append_char(pOut, c);
      iCol++;
      if( iCol>=70 ){
        blob_append(pOut, "=\n", 2);
        iCol = 0;
      }
    }else if( c=='\r' && zIn[0]=='\n' ){
      zIn++;
      blob_append(pOut, "\r\n", 2);
      iCol = 0;
    }else if( c=='\n' ){
      blob_append(pOut, "\r\n", 2);
      iCol = 0;
    }else{
      char x[3];
      x[0] = '=';
      x[1] = "0123456789ABCDEF"[(c>>4)&0xf];
      x[2] = "0123456789ABCDEF"[c&0xf];
      blob_append(pOut, x, 3);
      iCol += 3;
    }
  }
}

/*
** Come up with a unique filename in the zDir directory.
**
** Space to hold the filename is obtained from mprintf() and must
** be freed using fossil_free() by the caller.
................................................................................
    blob_init(&all, 0, 0);
    pOut = &all;
  }
  blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
  blob_appendf(pOut, "From: %s\r\n", p->zFrom);
  blob_add_final_newline(pBody);
  blob_appendf(pOut,"Content-Type: text/plain\r\n");
#if 0
  blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n");
  append_base64(pOut, pBody);
#else
  blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
  append_quoted(pOut, pBody);
#endif
  if( p->pStmt ){
    int i, rc;
    sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
    for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
      sqlite3_sleep(10);
    }
    rc = sqlite3_reset(p->pStmt);
................................................................................
}

/*
** Append the "unsubscribe" notification and other footer text to
** the end of an email alert being assemblied in pOut.
*/
void email_footer(Blob *pOut){
  blob_appendf(pOut, "\n-- \nTo unsubscribe: %s/unsubscribe\n",
     db_get("email-url","http://localhost:8080"));
}

/*
** COMMAND:  test-alert
**
** Usage: %fossil test-alert EVENTID ...
**
................................................................................
        );
      }
      nHit++;
      blob_append(&body, "\n", 1);
      blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
    }
    if( nHit==0 ) continue;
    blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
         zUrl, zCode);
    email_send(pSender,&hdr,&body);
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_zero(&hdr);
  blob_zero(&body);
  db_finalize(&q);
................................................................................
    blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
    email_send(pSender, &hdr, &body);
  }
  if( bAll || bAA ){
    Stmt q;
    int nUsed = blob_size(&body);
    const char *zURL =  db_get("email-url",0);
    db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
                   " WHERE sverified AND NOT sdonotcall %s",
                   bAll ? "" : " AND ssub LIKE '%a%'");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zCode = db_column_text(&q, 1);
      zTo = db_column_text(&q, 0);
      blob_truncate(&hdr, 0);
      blob_appendf(&hdr, "To: %s\nSubject: %s %s\n", zTo, zSub, zSubject);
      if( zURL ){
        blob_truncate(&body, nUsed);
        blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zURL, zCode);
      }
      email_send(pSender, &hdr, &body);
    }
    db_finalize(&q);
  }
  if( bTest2 ){
    /* If the URL is /announce/test2 instead of just /announce, then no