Fossil SCM

Use the email content parser to the prototype webmail page.

drh 2018-07-13 15:07 trunk
Commit 264223fc5981cd27b255e5f4170c2a81c9a24e69622bb6dc29c42c892e7087b2
3 files changed +6 +12 -1 +140 -20
+6
--- src/db.c
+++ src/db.c
@@ -519,10 +519,16 @@
519519
return mprintf("%s", db_column_text(pStmt, N));
520520
}
521521
void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
522522
blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
523523
sqlite3_column_bytes(pStmt->pStmt, N));
524
+}
525
+Blob db_column_text_as_blob(Stmt *pStmt, int N){
526
+ Blob x;
527
+ blob_init(&x, sqlite3_column_text(pStmt->pStmt,N),
528
+ sqlite3_column_bytes(pStmt->pStmt,N));
529
+ return x;
524530
}
525531
526532
/*
527533
** Initialize a blob to an ephemeral copy of the content of a
528534
** column in the current row. The data in the blob will become
529535
--- src/db.c
+++ src/db.c
@@ -519,10 +519,16 @@
519 return mprintf("%s", db_column_text(pStmt, N));
520 }
521 void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
522 blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
523 sqlite3_column_bytes(pStmt->pStmt, N));
 
 
 
 
 
 
524 }
525
526 /*
527 ** Initialize a blob to an ephemeral copy of the content of a
528 ** column in the current row. The data in the blob will become
529
--- src/db.c
+++ src/db.c
@@ -519,10 +519,16 @@
519 return mprintf("%s", db_column_text(pStmt, N));
520 }
521 void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
522 blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
523 sqlite3_column_bytes(pStmt->pStmt, N));
524 }
525 Blob db_column_text_as_blob(Stmt *pStmt, int N){
526 Blob x;
527 blob_init(&x, sqlite3_column_text(pStmt->pStmt,N),
528 sqlite3_column_bytes(pStmt->pStmt,N));
529 return x;
530 }
531
532 /*
533 ** Initialize a blob to an ephemeral copy of the content of a
534 ** column in the current row. The data in the blob will become
535
+12 -1
--- src/smtp.c
+++ src/smtp.c
@@ -652,11 +652,12 @@
652652
@ edate INT, -- Date received. Seconds since 1970
653653
@ efrom TEXT, -- Who is the email from
654654
@ emsgid INT, -- Raw email text
655655
@ ets INT, -- Transcript of the receiving SMTP session
656656
@ estate INT, -- Unread, read, starred, etc.
657
-@ esubject TEXT -- Subject line for display
657
+@ esubject TEXT, -- Subject line for display
658
+@ etags TEXT -- zero or more tags
658659
@ );
659660
@
660661
@ -- Information on how to deliver incoming email.
661662
@ CREATE TABLE IF NOT EXISTS repository.emailroute(
662663
@ eaddr TEXT PRIMARY KEY, -- Email address
@@ -683,10 +684,20 @@
683684
@ DROP TABLE IF EXISTS emailblob;
684685
@ DROP TABLE IF EXISTS emailbox;
685686
@ DROP TABLE IF EXISTS emailroute;
686687
@ DROP TABLE IF EXISTS emailqueue;
687688
;
689
+
690
+#if INTERFACE
691
+/*
692
+** Mailbox message states
693
+*/
694
+#define MSG_UNREAD 0
695
+#define MSG_READ 1
696
+#define MSG_TRASH 2
697
+#endif /* INTERFACE */
698
+
688699
689700
/*
690701
** Populate the schema of a database.
691702
**
692703
** eForce==0 Fast
693704
--- src/smtp.c
+++ src/smtp.c
@@ -652,11 +652,12 @@
652 @ edate INT, -- Date received. Seconds since 1970
653 @ efrom TEXT, -- Who is the email from
654 @ emsgid INT, -- Raw email text
655 @ ets INT, -- Transcript of the receiving SMTP session
656 @ estate INT, -- Unread, read, starred, etc.
657 @ esubject TEXT -- Subject line for display
 
658 @ );
659 @
660 @ -- Information on how to deliver incoming email.
661 @ CREATE TABLE IF NOT EXISTS repository.emailroute(
662 @ eaddr TEXT PRIMARY KEY, -- Email address
@@ -683,10 +684,20 @@
683 @ DROP TABLE IF EXISTS emailblob;
684 @ DROP TABLE IF EXISTS emailbox;
685 @ DROP TABLE IF EXISTS emailroute;
686 @ DROP TABLE IF EXISTS emailqueue;
687 ;
 
 
 
 
 
 
 
 
 
 
688
689 /*
690 ** Populate the schema of a database.
691 **
692 ** eForce==0 Fast
693
--- src/smtp.c
+++ src/smtp.c
@@ -652,11 +652,12 @@
652 @ edate INT, -- Date received. Seconds since 1970
653 @ efrom TEXT, -- Who is the email from
654 @ emsgid INT, -- Raw email text
655 @ ets INT, -- Transcript of the receiving SMTP session
656 @ estate INT, -- Unread, read, starred, etc.
657 @ esubject TEXT, -- Subject line for display
658 @ etags TEXT -- zero or more tags
659 @ );
660 @
661 @ -- Information on how to deliver incoming email.
662 @ CREATE TABLE IF NOT EXISTS repository.emailroute(
663 @ eaddr TEXT PRIMARY KEY, -- Email address
@@ -683,10 +684,20 @@
684 @ DROP TABLE IF EXISTS emailblob;
685 @ DROP TABLE IF EXISTS emailbox;
686 @ DROP TABLE IF EXISTS emailroute;
687 @ DROP TABLE IF EXISTS emailqueue;
688 ;
689
690 #if INTERFACE
691 /*
692 ** Mailbox message states
693 */
694 #define MSG_UNREAD 0
695 #define MSG_READ 1
696 #define MSG_TRASH 2
697 #endif /* INTERFACE */
698
699
700 /*
701 ** Populate the schema of a database.
702 **
703 ** eForce==0 Fast
704
+140 -20
--- src/webmail.c
+++ src/webmail.c
@@ -117,10 +117,33 @@
117117
int i;
118118
for(i=0; z[i] && (z[i]!='\n' || z[i+1]==' ' || z[i+1]=='\t'); i++){}
119119
if( z[i]=='\n' ) i++;
120120
return i;
121121
}
122
+
123
+/*
124
+** Look for a parameter of the form NAME=VALUE in the given email
125
+** header line. Return a copy of VALUE in space obtained from
126
+** fossil_malloc(). Or return NULL if there is no such parameter.
127
+*/
128
+static char *email_hdr_value(const char *z, const char *zName){
129
+ int nName = (int)strlen(zName);
130
+ int i;
131
+ const char *z2 = strstr(z, zName);
132
+ if( z2==0 ) return 0;
133
+ z2 += nName;
134
+ if( z2[0]!='=' ) return 0;
135
+ z2++;
136
+ if( z2[0]=='"' ){
137
+ z2++;
138
+ for(i=0; z2[i] && z2[i]!='"'; i++){}
139
+ if( z2[i]!='"' ) return 0;
140
+ }else{
141
+ for(i=0; z2[i] && !fossil_isspace(z2[i]); i++){}
142
+ }
143
+ return mprintf("%.*s", i, z2);
144
+}
122145
123146
/*
124147
** Return a pointer to the first non-whitespace character in z
125148
*/
126149
static const char *firstToken(const char *z){
@@ -178,11 +201,17 @@
178201
pBody->encoding = EMAILENC_QUOTED;
179202
}else{
180203
pBody->encoding = EMAILENC_NONE;
181204
}
182205
}
183
- if( bAddHeader ) emailtoc_new_header_line(p, z+i);
206
+ if( bAddHeader ){
207
+ emailtoc_new_header_line(p, z+i);
208
+ }else if( sqlite3_strnicmp(z+i, "Content-Disposition:", 20)==0 ){
209
+ /* 123456789 123456789 */
210
+ fossil_free(pBody->zFilename);
211
+ pBody->zFilename = email_hdr_value(z+i, "filename");
212
+ }
184213
i += n;
185214
}
186215
if( multipartBody ){
187216
p->nBody--;
188217
emailtoc_add_multipart(p, z+i);
@@ -204,16 +233,24 @@
204233
){
205234
int nB; /* Size of the boundary string */
206235
int iStart; /* Start of the coding region past boundary mark */
207236
int i; /* Loop index */
208237
char *zBoundary = 0; /* Boundary marker */
238
+
239
+ /* Skip forward to the beginning of the boundary mark. The boundary
240
+ ** mark always begins with "--" */
241
+ while( z[0]!='-' || z[1]!='-' ){
242
+ while( z[0] && z[0]!='\n' ) z++;
243
+ if( z[0]==0 ) return;
244
+ z++;
245
+ }
209246
210247
/* Find the length of the boundary mark. */
211
- while( fossil_isspace(z[0]) ) z++;
212248
zBoundary = z;
213249
for(nB=0; z[nB] && !fossil_isspace(z[nB]); nB++){}
214250
if( nB==0 ) return;
251
+
215252
z += nB;
216253
while( fossil_isspace(z[0]) ) z++;
217254
zBoundary[nB] = 0;
218255
for(i=iStart=0; z[i]; i++){
219256
if( z[i]=='\n' && strncmp(z+i+1, zBoundary, nB)==0 ){
@@ -224,11 +261,10 @@
224261
while( fossil_isspace(z[iStart]) ) iStart++;
225262
i = iStart;
226263
}
227264
}
228265
}
229
-
230266
231267
/*
232268
** Compute a table-of-contents (EmailToc) for the email message
233269
** provided on the input.
234270
**
@@ -242,10 +278,29 @@
242278
blob_terminate(pEmail);
243279
z = blob_buffer(pEmail);
244280
emailtoc_add_multipart_segment(p, z, 1);
245281
return p;
246282
}
283
+
284
+/*
285
+** Inplace-unfolding of an email header line.
286
+**
287
+** Actually - this routine works by converting all contiguous sequences
288
+** of whitespace into a single space character.
289
+*/
290
+static void email_hdr_unfold(char *z){
291
+ int i, j;
292
+ char c;
293
+ for(i=j=0; (c = z[i])!=0; i++){
294
+ if( fossil_isspace(c) ){
295
+ c = ' ';
296
+ if( j && z[j-1]==' ' ) continue;
297
+ }
298
+ z[j++] = c;
299
+ }
300
+ z[j] = 0;
301
+}
247302
248303
/*
249304
** COMMAND: test-decode-email
250305
**
251306
** Usage: %fossil test-decode-email FILE
@@ -262,15 +317,21 @@
262317
blob_read_from_file(&email, g.argv[2], ExtFILE);
263318
p = emailtoc_from_email(&email);
264319
fossil_print("%d header line and %d content segments\n",
265320
p->nHdr, p->nBody);
266321
for(i=0; i<p->nHdr; i++){
322
+ email_hdr_unfold(p->azHdr[i]);
267323
fossil_print("%3d: %s\n", i, p->azHdr[i]);
268324
}
269325
for(i=0; i<p->nBody; i++){
270
- fossil_print("\nBODY %d mime \"%s\" encoding %d:\n",
326
+ fossil_print("\nBODY %d mime \"%s\" encoding %d",
271327
i, p->aBody[i].zMimetype, p->aBody[i].encoding);
328
+ if( p->aBody[i].zFilename ){
329
+ fossil_print(" filename \"%s\"", p->aBody[i].zFilename);
330
+ }
331
+ fossil_print("\n");
332
+ if( strncmp(p->aBody[i].zMimetype,"text/",5)!=0 ) continue;
272333
switch( p->aBody[i].encoding ){
273334
case EMAILENC_B64: {
274335
int n = 0;
275336
decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
276337
fossil_print("%s", p->aBody[i].zContent);
@@ -303,10 +364,12 @@
303364
void webmail_page(void){
304365
int emailid;
305366
Stmt q;
306367
Blob sql;
307368
int showAll = 0;
369
+ const char *zUser = 0;
370
+ HQuery url;
308371
login_check_credentials();
309372
if( g.zLogin==0 ){
310373
login_needed(0);
311374
return;
312375
}
@@ -316,11 +379,23 @@
316379
style_footer();
317380
return;
318381
}
319382
add_content_sql_commands(g.db);
320383
emailid = atoi(PD("id","0"));
384
+ url_initialize(&url, "webmail");
385
+ if( g.perm.Admin ){
386
+ zUser = P("user");
387
+ if( zUser ){
388
+ url_add_parameter(&url, "user", zUser);
389
+ if( fossil_strcmp(zUser,"*")==0 ){
390
+ showAll = 1;
391
+ zUser = 0;
392
+ }
393
+ }
394
+ }
321395
if( emailid>0 ){
396
+ style_submenu_element("Index", "%s", url_render(&url,"id",0,0,0));
322397
blob_init(&sql, 0, 0);
323398
blob_append_sql(&sql, "SELECT decompress(etxt)"
324399
" FROM emailblob WHERE emailid=%d",
325400
emailid);
326401
if( !g.perm.Admin ){
@@ -328,12 +403,54 @@
328403
" euser=%Q AND emsgid=emailid)", g.zLogin);
329404
}
330405
db_prepare_blob(&q, &sql);
331406
blob_reset(&sql);
332407
if( db_step(&q)==SQLITE_ROW ){
408
+ Blob msg = db_column_text_as_blob(&q, 0);
409
+ url_add_parameter(&url, "id", P("id"));
333410
style_header("Message %d",emailid);
334
- @ <pre>%h(db_column_text(&q, 0))</pre>
411
+ if( PB("raw") ){
412
+ @ <pre>%h(db_column_text(&q, 0))</pre>
413
+ style_submenu_element("Decoded", "%s", url_render(&url,"raw",0,0,0));
414
+ }else{
415
+ EmailToc *p = emailtoc_from_email(&msg);
416
+ int i, j;
417
+ style_submenu_element("Raw", "%s", url_render(&url,"raw","1",0,0));
418
+ @ <p>
419
+ for(i=0; i<p->nHdr; i++){
420
+ char *z = p->azHdr[i];
421
+ email_hdr_unfold(z);
422
+ for(j=0; z[j] && z[j]!=':'; j++){}
423
+ if( z[j]!=':' ){
424
+ @ %h(z)<br>
425
+ }else{
426
+ z[j] = 0;
427
+ @ <b>%h(z):</b> %h(z+j+1)<br>
428
+ }
429
+ }
430
+ for(i=0; i<p->nBody; i++){
431
+ @ <hr><b>Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \
432
+ if( p->aBody[i].zFilename ){
433
+ @ "%h(p->aBody[i].zFilename)"
434
+ }
435
+ @ </b>
436
+ if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue;
437
+ switch( p->aBody[i].encoding ){
438
+ case EMAILENC_B64: {
439
+ int n = 0;
440
+ decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
441
+ break;
442
+ }
443
+ case EMAILENC_QUOTED: {
444
+ int n = 0;
445
+ decodeQuotedPrintable(p->aBody[i].zContent, &n);
446
+ break;
447
+ }
448
+ }
449
+ @ <pre>%h(p->aBody[i].zContent)</pre>
450
+ }
451
+ }
335452
style_footer();
336453
db_finalize(&q);
337454
return;
338455
}
339456
db_finalize(&q);
@@ -343,42 +460,45 @@
343460
blob_append_sql(&sql,
344461
/* 0 1 2 3 4 5 */
345462
"SELECT efrom, datetime(edate,'unixepoch'), estate, esubject, emsgid, euser"
346463
" FROM emailbox"
347464
);
348
- if( g.perm.Admin ){
349
- const char *zUser = P("user");
350
- if( P("all")!=0 ){
351
- /* Show all email messages */
352
- showAll = 1;
353
- }else{
354
- style_submenu_element("All", "%R/webmail?all");
355
- if( zUser ){
356
- blob_append_sql(&sql, " WHERE euser=%Q", zUser);
357
- }else{
358
- blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
359
- }
465
+ if( showAll ){
466
+ style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0));
467
+ }else if( zUser!=0 ){
468
+ style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0));
469
+ if( fossil_strcmp(zUser, g.zLogin)!=0 ){
470
+ style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0));
471
+ }
472
+ if( zUser ){
473
+ blob_append_sql(&sql, " WHERE euser=%Q", zUser);
474
+ }else{
475
+ blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
360476
}
361477
}else{
478
+ if( g.perm.Admin ){
479
+ style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0));
480
+ }
362481
blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
363482
}
364483
blob_append_sql(&sql, " ORDER BY edate DESC limit 50");
365484
db_prepare_blob(&q, &sql);
366485
blob_reset(&sql);
367486
@ <ol>
368487
while( db_step(&q)==SQLITE_ROW ){
369
- int emailid = db_column_int(&q,4);
488
+ char *zId = db_column_text(&q,4);
370489
const char *zFrom = db_column_text(&q, 0);
371490
const char *zDate = db_column_text(&q, 1);
372491
const char *zSubject = db_column_text(&q, 3);
373492
@ <li>
374493
if( showAll ){
375494
const char *zTo = db_column_text(&q,5);
376
- @ <a href="%R/webmail?user=%t(zTo)">%h(zTo)</a>:
495
+ @ <a href="%s(url_render(&url,"user",zTo,0,0))">%h(zTo)</a>:
377496
}
378
- @ <a href="%R/webmail?id=%d(emailid)">%h(zFrom) &rarr; %h(zSubject)</a>
497
+ @ <a href="%s(url_render(&url,"id",zId,0,0))">\
498
+ @ %h(zFrom) &rarr; %h(zSubject)</a>
379499
@ %h(zDate)
380500
}
381501
db_finalize(&q);
382502
@ </ol>
383503
style_footer();
384504
}
385505
--- src/webmail.c
+++ src/webmail.c
@@ -117,10 +117,33 @@
117 int i;
118 for(i=0; z[i] && (z[i]!='\n' || z[i+1]==' ' || z[i+1]=='\t'); i++){}
119 if( z[i]=='\n' ) i++;
120 return i;
121 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
123 /*
124 ** Return a pointer to the first non-whitespace character in z
125 */
126 static const char *firstToken(const char *z){
@@ -178,11 +201,17 @@
178 pBody->encoding = EMAILENC_QUOTED;
179 }else{
180 pBody->encoding = EMAILENC_NONE;
181 }
182 }
183 if( bAddHeader ) emailtoc_new_header_line(p, z+i);
 
 
 
 
 
 
184 i += n;
185 }
186 if( multipartBody ){
187 p->nBody--;
188 emailtoc_add_multipart(p, z+i);
@@ -204,16 +233,24 @@
204 ){
205 int nB; /* Size of the boundary string */
206 int iStart; /* Start of the coding region past boundary mark */
207 int i; /* Loop index */
208 char *zBoundary = 0; /* Boundary marker */
 
 
 
 
 
 
 
 
209
210 /* Find the length of the boundary mark. */
211 while( fossil_isspace(z[0]) ) z++;
212 zBoundary = z;
213 for(nB=0; z[nB] && !fossil_isspace(z[nB]); nB++){}
214 if( nB==0 ) return;
 
215 z += nB;
216 while( fossil_isspace(z[0]) ) z++;
217 zBoundary[nB] = 0;
218 for(i=iStart=0; z[i]; i++){
219 if( z[i]=='\n' && strncmp(z+i+1, zBoundary, nB)==0 ){
@@ -224,11 +261,10 @@
224 while( fossil_isspace(z[iStart]) ) iStart++;
225 i = iStart;
226 }
227 }
228 }
229
230
231 /*
232 ** Compute a table-of-contents (EmailToc) for the email message
233 ** provided on the input.
234 **
@@ -242,10 +278,29 @@
242 blob_terminate(pEmail);
243 z = blob_buffer(pEmail);
244 emailtoc_add_multipart_segment(p, z, 1);
245 return p;
246 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
248 /*
249 ** COMMAND: test-decode-email
250 **
251 ** Usage: %fossil test-decode-email FILE
@@ -262,15 +317,21 @@
262 blob_read_from_file(&email, g.argv[2], ExtFILE);
263 p = emailtoc_from_email(&email);
264 fossil_print("%d header line and %d content segments\n",
265 p->nHdr, p->nBody);
266 for(i=0; i<p->nHdr; i++){
 
267 fossil_print("%3d: %s\n", i, p->azHdr[i]);
268 }
269 for(i=0; i<p->nBody; i++){
270 fossil_print("\nBODY %d mime \"%s\" encoding %d:\n",
271 i, p->aBody[i].zMimetype, p->aBody[i].encoding);
 
 
 
 
 
272 switch( p->aBody[i].encoding ){
273 case EMAILENC_B64: {
274 int n = 0;
275 decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
276 fossil_print("%s", p->aBody[i].zContent);
@@ -303,10 +364,12 @@
303 void webmail_page(void){
304 int emailid;
305 Stmt q;
306 Blob sql;
307 int showAll = 0;
 
 
308 login_check_credentials();
309 if( g.zLogin==0 ){
310 login_needed(0);
311 return;
312 }
@@ -316,11 +379,23 @@
316 style_footer();
317 return;
318 }
319 add_content_sql_commands(g.db);
320 emailid = atoi(PD("id","0"));
 
 
 
 
 
 
 
 
 
 
 
321 if( emailid>0 ){
 
322 blob_init(&sql, 0, 0);
323 blob_append_sql(&sql, "SELECT decompress(etxt)"
324 " FROM emailblob WHERE emailid=%d",
325 emailid);
326 if( !g.perm.Admin ){
@@ -328,12 +403,54 @@
328 " euser=%Q AND emsgid=emailid)", g.zLogin);
329 }
330 db_prepare_blob(&q, &sql);
331 blob_reset(&sql);
332 if( db_step(&q)==SQLITE_ROW ){
 
 
333 style_header("Message %d",emailid);
334 @ <pre>%h(db_column_text(&q, 0))</pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335 style_footer();
336 db_finalize(&q);
337 return;
338 }
339 db_finalize(&q);
@@ -343,42 +460,45 @@
343 blob_append_sql(&sql,
344 /* 0 1 2 3 4 5 */
345 "SELECT efrom, datetime(edate,'unixepoch'), estate, esubject, emsgid, euser"
346 " FROM emailbox"
347 );
348 if( g.perm.Admin ){
349 const char *zUser = P("user");
350 if( P("all")!=0 ){
351 /* Show all email messages */
352 showAll = 1;
353 }else{
354 style_submenu_element("All", "%R/webmail?all");
355 if( zUser ){
356 blob_append_sql(&sql, " WHERE euser=%Q", zUser);
357 }else{
358 blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
359 }
360 }
361 }else{
 
 
 
362 blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
363 }
364 blob_append_sql(&sql, " ORDER BY edate DESC limit 50");
365 db_prepare_blob(&q, &sql);
366 blob_reset(&sql);
367 @ <ol>
368 while( db_step(&q)==SQLITE_ROW ){
369 int emailid = db_column_int(&q,4);
370 const char *zFrom = db_column_text(&q, 0);
371 const char *zDate = db_column_text(&q, 1);
372 const char *zSubject = db_column_text(&q, 3);
373 @ <li>
374 if( showAll ){
375 const char *zTo = db_column_text(&q,5);
376 @ <a href="%R/webmail?user=%t(zTo)">%h(zTo)</a>:
377 }
378 @ <a href="%R/webmail?id=%d(emailid)">%h(zFrom) &rarr; %h(zSubject)</a>
 
379 @ %h(zDate)
380 }
381 db_finalize(&q);
382 @ </ol>
383 style_footer();
384 }
385
--- src/webmail.c
+++ src/webmail.c
@@ -117,10 +117,33 @@
117 int i;
118 for(i=0; z[i] && (z[i]!='\n' || z[i+1]==' ' || z[i+1]=='\t'); i++){}
119 if( z[i]=='\n' ) i++;
120 return i;
121 }
122
123 /*
124 ** Look for a parameter of the form NAME=VALUE in the given email
125 ** header line. Return a copy of VALUE in space obtained from
126 ** fossil_malloc(). Or return NULL if there is no such parameter.
127 */
128 static char *email_hdr_value(const char *z, const char *zName){
129 int nName = (int)strlen(zName);
130 int i;
131 const char *z2 = strstr(z, zName);
132 if( z2==0 ) return 0;
133 z2 += nName;
134 if( z2[0]!='=' ) return 0;
135 z2++;
136 if( z2[0]=='"' ){
137 z2++;
138 for(i=0; z2[i] && z2[i]!='"'; i++){}
139 if( z2[i]!='"' ) return 0;
140 }else{
141 for(i=0; z2[i] && !fossil_isspace(z2[i]); i++){}
142 }
143 return mprintf("%.*s", i, z2);
144 }
145
146 /*
147 ** Return a pointer to the first non-whitespace character in z
148 */
149 static const char *firstToken(const char *z){
@@ -178,11 +201,17 @@
201 pBody->encoding = EMAILENC_QUOTED;
202 }else{
203 pBody->encoding = EMAILENC_NONE;
204 }
205 }
206 if( bAddHeader ){
207 emailtoc_new_header_line(p, z+i);
208 }else if( sqlite3_strnicmp(z+i, "Content-Disposition:", 20)==0 ){
209 /* 123456789 123456789 */
210 fossil_free(pBody->zFilename);
211 pBody->zFilename = email_hdr_value(z+i, "filename");
212 }
213 i += n;
214 }
215 if( multipartBody ){
216 p->nBody--;
217 emailtoc_add_multipart(p, z+i);
@@ -204,16 +233,24 @@
233 ){
234 int nB; /* Size of the boundary string */
235 int iStart; /* Start of the coding region past boundary mark */
236 int i; /* Loop index */
237 char *zBoundary = 0; /* Boundary marker */
238
239 /* Skip forward to the beginning of the boundary mark. The boundary
240 ** mark always begins with "--" */
241 while( z[0]!='-' || z[1]!='-' ){
242 while( z[0] && z[0]!='\n' ) z++;
243 if( z[0]==0 ) return;
244 z++;
245 }
246
247 /* Find the length of the boundary mark. */
 
248 zBoundary = z;
249 for(nB=0; z[nB] && !fossil_isspace(z[nB]); nB++){}
250 if( nB==0 ) return;
251
252 z += nB;
253 while( fossil_isspace(z[0]) ) z++;
254 zBoundary[nB] = 0;
255 for(i=iStart=0; z[i]; i++){
256 if( z[i]=='\n' && strncmp(z+i+1, zBoundary, nB)==0 ){
@@ -224,11 +261,10 @@
261 while( fossil_isspace(z[iStart]) ) iStart++;
262 i = iStart;
263 }
264 }
265 }
 
266
267 /*
268 ** Compute a table-of-contents (EmailToc) for the email message
269 ** provided on the input.
270 **
@@ -242,10 +278,29 @@
278 blob_terminate(pEmail);
279 z = blob_buffer(pEmail);
280 emailtoc_add_multipart_segment(p, z, 1);
281 return p;
282 }
283
284 /*
285 ** Inplace-unfolding of an email header line.
286 **
287 ** Actually - this routine works by converting all contiguous sequences
288 ** of whitespace into a single space character.
289 */
290 static void email_hdr_unfold(char *z){
291 int i, j;
292 char c;
293 for(i=j=0; (c = z[i])!=0; i++){
294 if( fossil_isspace(c) ){
295 c = ' ';
296 if( j && z[j-1]==' ' ) continue;
297 }
298 z[j++] = c;
299 }
300 z[j] = 0;
301 }
302
303 /*
304 ** COMMAND: test-decode-email
305 **
306 ** Usage: %fossil test-decode-email FILE
@@ -262,15 +317,21 @@
317 blob_read_from_file(&email, g.argv[2], ExtFILE);
318 p = emailtoc_from_email(&email);
319 fossil_print("%d header line and %d content segments\n",
320 p->nHdr, p->nBody);
321 for(i=0; i<p->nHdr; i++){
322 email_hdr_unfold(p->azHdr[i]);
323 fossil_print("%3d: %s\n", i, p->azHdr[i]);
324 }
325 for(i=0; i<p->nBody; i++){
326 fossil_print("\nBODY %d mime \"%s\" encoding %d",
327 i, p->aBody[i].zMimetype, p->aBody[i].encoding);
328 if( p->aBody[i].zFilename ){
329 fossil_print(" filename \"%s\"", p->aBody[i].zFilename);
330 }
331 fossil_print("\n");
332 if( strncmp(p->aBody[i].zMimetype,"text/",5)!=0 ) continue;
333 switch( p->aBody[i].encoding ){
334 case EMAILENC_B64: {
335 int n = 0;
336 decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
337 fossil_print("%s", p->aBody[i].zContent);
@@ -303,10 +364,12 @@
364 void webmail_page(void){
365 int emailid;
366 Stmt q;
367 Blob sql;
368 int showAll = 0;
369 const char *zUser = 0;
370 HQuery url;
371 login_check_credentials();
372 if( g.zLogin==0 ){
373 login_needed(0);
374 return;
375 }
@@ -316,11 +379,23 @@
379 style_footer();
380 return;
381 }
382 add_content_sql_commands(g.db);
383 emailid = atoi(PD("id","0"));
384 url_initialize(&url, "webmail");
385 if( g.perm.Admin ){
386 zUser = P("user");
387 if( zUser ){
388 url_add_parameter(&url, "user", zUser);
389 if( fossil_strcmp(zUser,"*")==0 ){
390 showAll = 1;
391 zUser = 0;
392 }
393 }
394 }
395 if( emailid>0 ){
396 style_submenu_element("Index", "%s", url_render(&url,"id",0,0,0));
397 blob_init(&sql, 0, 0);
398 blob_append_sql(&sql, "SELECT decompress(etxt)"
399 " FROM emailblob WHERE emailid=%d",
400 emailid);
401 if( !g.perm.Admin ){
@@ -328,12 +403,54 @@
403 " euser=%Q AND emsgid=emailid)", g.zLogin);
404 }
405 db_prepare_blob(&q, &sql);
406 blob_reset(&sql);
407 if( db_step(&q)==SQLITE_ROW ){
408 Blob msg = db_column_text_as_blob(&q, 0);
409 url_add_parameter(&url, "id", P("id"));
410 style_header("Message %d",emailid);
411 if( PB("raw") ){
412 @ <pre>%h(db_column_text(&q, 0))</pre>
413 style_submenu_element("Decoded", "%s", url_render(&url,"raw",0,0,0));
414 }else{
415 EmailToc *p = emailtoc_from_email(&msg);
416 int i, j;
417 style_submenu_element("Raw", "%s", url_render(&url,"raw","1",0,0));
418 @ <p>
419 for(i=0; i<p->nHdr; i++){
420 char *z = p->azHdr[i];
421 email_hdr_unfold(z);
422 for(j=0; z[j] && z[j]!=':'; j++){}
423 if( z[j]!=':' ){
424 @ %h(z)<br>
425 }else{
426 z[j] = 0;
427 @ <b>%h(z):</b> %h(z+j+1)<br>
428 }
429 }
430 for(i=0; i<p->nBody; i++){
431 @ <hr><b>Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \
432 if( p->aBody[i].zFilename ){
433 @ "%h(p->aBody[i].zFilename)"
434 }
435 @ </b>
436 if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue;
437 switch( p->aBody[i].encoding ){
438 case EMAILENC_B64: {
439 int n = 0;
440 decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
441 break;
442 }
443 case EMAILENC_QUOTED: {
444 int n = 0;
445 decodeQuotedPrintable(p->aBody[i].zContent, &n);
446 break;
447 }
448 }
449 @ <pre>%h(p->aBody[i].zContent)</pre>
450 }
451 }
452 style_footer();
453 db_finalize(&q);
454 return;
455 }
456 db_finalize(&q);
@@ -343,42 +460,45 @@
460 blob_append_sql(&sql,
461 /* 0 1 2 3 4 5 */
462 "SELECT efrom, datetime(edate,'unixepoch'), estate, esubject, emsgid, euser"
463 " FROM emailbox"
464 );
465 if( showAll ){
466 style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0));
467 }else if( zUser!=0 ){
468 style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0));
469 if( fossil_strcmp(zUser, g.zLogin)!=0 ){
470 style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0));
471 }
472 if( zUser ){
473 blob_append_sql(&sql, " WHERE euser=%Q", zUser);
474 }else{
475 blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
 
476 }
477 }else{
478 if( g.perm.Admin ){
479 style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0));
480 }
481 blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
482 }
483 blob_append_sql(&sql, " ORDER BY edate DESC limit 50");
484 db_prepare_blob(&q, &sql);
485 blob_reset(&sql);
486 @ <ol>
487 while( db_step(&q)==SQLITE_ROW ){
488 char *zId = db_column_text(&q,4);
489 const char *zFrom = db_column_text(&q, 0);
490 const char *zDate = db_column_text(&q, 1);
491 const char *zSubject = db_column_text(&q, 3);
492 @ <li>
493 if( showAll ){
494 const char *zTo = db_column_text(&q,5);
495 @ <a href="%s(url_render(&url,"user",zTo,0,0))">%h(zTo)</a>:
496 }
497 @ <a href="%s(url_render(&url,"id",zId,0,0))">\
498 @ %h(zFrom) &rarr; %h(zSubject)</a>
499 @ %h(zDate)
500 }
501 db_finalize(&q);
502 @ </ol>
503 style_footer();
504 }
505

Keyboard Shortcuts

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