Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -343,11 +343,11 @@ CGIDEBUG(("DONE\n")); /* After the webpage has been sent, do any useful background ** processing. */ - if( iReplyStatus==200 && fossil_strcmp(zContentType,"test/html")==0 ){ + if( iReplyStatus==200 && fossil_strcmp(zContentType,"text/html")==0 ){ email_auto_exec(); } } /* Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -174,15 +174,18 @@ /* ** Silently add the filename and line number as parameter to each ** db_begin_transaction call. */ #if INTERFACE -#define db_begin_transaction() db_begin_transaction_real(__FILE__,__LINE__) +#define db_begin_transaction() db_begin_transaction_real(__FILE__,__LINE__) +#define db_begin_write() db_begin_write_real(__FILE__,__LINE__) +#define db_commit_transaction() db_end_transaction(0) +#define db_rollback_transaction() db_end_transaction(1) #endif /* -** Begin and end a nested transaction +** Begin a nested transaction */ void db_begin_transaction_real(const char *zStartFile, int iStartLine){ if( db.nBegin==0 ){ db_multi_exec("BEGIN"); sqlite3_commit_hook(g.db, db_verify_at_commit, 0); @@ -191,10 +194,32 @@ db.zStartFile = zStartFile; db.iStartLine = iStartLine; } db.nBegin++; } +/* +** Begin a new transaction for writing. +*/ +void db_begin_write_real(const char *zStartFile, int iStartLine){ + if( db.nBegin==0 ){ + db_multi_exec("BEGIN IMMEDIATE"); + sqlite3_commit_hook(g.db, db_verify_at_commit, 0); + db.nPriorChanges = sqlite3_total_changes(g.db); + db.doRollback = 0; + db.zStartFile = zStartFile; + db.iStartLine = iStartLine; + }else{ + fossil_warning("read txn at %s:%d might cause SQLITE_BUSY " + "for the write txn at %s:%d", + db.zStartFile, db.iStartLine, zStartFile, iStartLine); + } + db.nBegin++; +} + +/* End a transaction previously started using db_begin_transaction() +** or db_begin_write(). +*/ void db_end_transaction(int rollbackFlag){ if( g.db==0 ) return; if( db.nBegin<=0 ){ fossil_warning("Extra call to db_end_transaction"); return; Index: src/email.c ================================================================== --- src/email.c +++ src/email.c @@ -204,10 +204,20 @@ db_begin_transaction(); email_submenu_common(); style_submenu_element("Send Announcement","%R/announce"); style_header("Email Notification Setup"); + @

Status

+ @ + if( email_enabled() ){ + stats_for_email(); + }else{ + @ + } + @
Disabled
+ @
+ @

Configuration

@
@
login_insert_csrf_secret(); entry_attribute("Canonical Server URL", 40, "email-url", @@ -2092,13 +2102,13 @@ if( db_transaction_nesting_depth()!=0 ){ fossil_warning("Called email_auto_exec() from within transaction " "started at %z", db_transaction_start_point()); return; } - db_begin_transaction(); - if( !email_tables_exist() ) goto autoexec_done; - if( !db_get_boolean("email-autoexec",0) ) goto autoexec_done; + if( !email_tables_exist() ) return; + if( !db_get_boolean("email-autoexec",0) ) return; + db_begin_write(); email_send_alerts(0); iJulianDay = db_int(0, "SELECT julianday('now')"); if( iJulianDay>db_get_int("email-last-digest",0) ){ if( db_transaction_nesting_depth()!=1 ){ fossil_warning("Transaction nesting error prior to digest processing"); @@ -2105,13 +2115,11 @@ }else{ db_set_int("email-last-digest",iJulianDay,0); email_send_alerts(SENDALERT_DIGEST); } } - -autoexec_done: - db_end_transaction(0); + db_commit_transaction(); } /* ** WEBPAGE: contact_admin ** Index: src/smtp.c ================================================================== --- src/smtp.c +++ src/smtp.c @@ -638,24 +638,25 @@ static const char zEmailSchema[] = @ -- bulk storage is in a separate table. This table can store either @ -- the body of email messages or transcripts of smtp sessions. @ CREATE TABLE IF NOT EXISTS repository.emailblob( @ emailid INTEGER PRIMARY KEY, -- numeric idea for the entry +@ enref INT, -- Number of references to this blob @ ets INT, -- Corresponding transcript, or NULL @ etime INT, -- insertion time, secs since 1970 @ etxt TEXT -- content of this entry @ ); @ @ -- One row for each mailbox entry. All users emails are stored in @ -- this same table. @ CREATE TABLE IF NOT EXISTS repository.emailbox( +@ ebid INTEGER PRIMARY KEY, -- Unique id for each mailbox entry @ euser TEXT, -- User who received this email @ edate INT, -- Date received. Seconds since 1970 @ efrom TEXT, -- Who is the email from @ emsgid INT, -- Raw email text -@ ets INT, -- Transcript of the receiving SMTP session -@ estate INT, -- Unread, read, starred, etc. +@ estate INT, -- 0: Unread, 1: read, 2: trash 3: sent @ esubject TEXT, -- Subject line for display @ etags TEXT -- zero or more tags @ ); @ @ -- Information on how to deliver incoming email. @@ -673,10 +674,29 @@ @ ectime INT, -- Time enqueued. Seconds since 1970 @ emtime INT, -- Time of last send attempt. Sec since 1970 @ ensend INT, -- Number of send attempts @ ets INT -- Transcript of last failed attempt @ ); +@ +@ -- Triggers to automatically keep the emailblob.enref field up to date +@ -- as entries in the emailblob, emailbox, and emailoutq tables are +@ -- deleted. +@ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1 +@ AFTER DELETE ON emailblob BEGIN +@ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.ets; +@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets; +@ END; +@ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1 +@ AFTER DELETE ON emailbox BEGIN +@ DELETE FROM emailblob WHERE enref<=1 AND emailid=old.emsgid; +@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid; +@ END; +@ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1 +@ AFTER DELETE ON emailoutq BEGIN +@ DELETE FROM emailblob WHERE enref<=1 AND emailid IN (old.ets,old.emsgid); +@ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid); +@ END; ; /* ** Code used to delete the email tables. */ @@ -828,10 +848,12 @@ struct SmtpTo { char *z; /* Address in each RCPT TO line */ int okRemote; /* zTo can be in another domain */ } *aTo; u32 srvrFlags; /* Control flags */ + int nEts; /* Number of references to the transcript */ + int nRef; /* Number of references to idMsg */ Blob msg; /* Content following DATA */ Blob transcript; /* Session transcript */ }; #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ @@ -999,10 +1021,11 @@ "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime," "emtime,ensend)" "VALUES(%Q,%Q,%Q,%lld,now(),0,0)", zAddr+i+1, p->zFrom, zAddr, p->idMsg ); + p->nRef++; } } return; } blob_init(&policy, zPolicy, -1); @@ -1013,16 +1036,17 @@ if( blob_size(&tail)==0 ) continue; if( blob_eq_str(&token, "mbox", 4) ){ Blob subj; email_header_value(&p->msg, "subject", &subj); db_multi_exec( - "INSERT INTO emailbox(euser,edate,efrom,emsgid,ets,estate,esubject)" - " VALUES(%Q,now(),%Q,%lld,%lld,0,%Q)", - blob_str(&tail), p->zFrom, p->idMsg, p->idTranscript, + "INSERT INTO emailbox(euser,edate,efrom,emsgid,estate,esubject)" + " VALUES(%Q,now(),%Q,%lld,0,%Q)", + blob_str(&tail), p->zFrom, p->idMsg, blob_str(&subj) ); blob_reset(&subj); + p->nRef++; } if( blob_eq_str(&token, "forward", 7) ){ smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1); } blob_reset(&tail); @@ -1039,38 +1063,43 @@ if( p->zFrom && p->nTo && blob_size(&p->msg) && (p->srvrFlags & SMTPSRV_DRYRUN)==0 ){ - db_begin_transaction(); + db_begin_write(); if( p->idTranscript==0 ) smtp_server_schema(0); + p->nRef = 0; db_prepare(&s, - "INSERT INTO emailblob(ets,etime,etxt)" - " VALUES(:ets,now(),compress(:etxt))" + "INSERT INTO emailblob(ets,etime,etxt,enref)" + " VALUES(:ets,now(),compress(:etxt),:enref)" ); + p->nEts++; if( !bFinish && p->idTranscript==0 ){ db_bind_null(&s, ":ets"); db_bind_null(&s, ":etxt"); + db_bind_int(&s, ":enref", 0); db_step(&s); db_reset(&s); p->idTranscript = db_last_insert_rowid(); }else if( bFinish ){ if( p->idTranscript ){ db_multi_exec( - "UPDATE emailblob SET etxt=compress(%Q)" + "UPDATE emailblob SET etxt=compress(%Q), enref=%d" " WHERE emailid=%lld", - blob_str(&p->transcript), p->idTranscript); + blob_str(&p->transcript), p->nEts, p->idTranscript); }else{ db_bind_null(&s, ":ets"); db_bind_str(&s, ":etxt", &p->transcript); + db_bind_int(&s, ":enref", p->nEts); db_step(&s); db_reset(&s); p->idTranscript = db_last_insert_rowid(); } } db_bind_int64(&s, ":ets", p->idTranscript); db_bind_str(&s, ":etxt", &p->msg); + db_bind_int(&s, ":enref", 0); db_step(&s); db_finalize(&s); p->idMsg = db_last_insert_rowid(); /* make entries in emailbox and emailoutq */ @@ -1077,16 +1106,92 @@ for(i=0; inTo; i++){ int okRemote = p->aTo[i].okRemote; p->aTo[i].okRemote = 1; smtp_server_send_one_user(p, p->aTo[i].z, okRemote); } + + /* Fix up the emailblob.enref field of the email message body */ + if( p->nRef ){ + db_multi_exec( + "UPDATE emailblob SET enref=%d WHERE emailid=%lld", + p->nRef, p->idMsg + ); + }else{ + db_multi_exec( + "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg + ); + } /* Finish the transaction after all changes are implemented */ - db_end_transaction(0); + db_commit_transaction(); } smtp_server_clear(p, SMTPSRV_CLEAR_MSG); } + +/* +** COMMAND: test-emailblob-refcheck +** +** Usage: %fossil test-emailblob-refcheck [--repair] [--full] +** +** Verify that the emailblob.enref field is correct. Report any errors. +** Use the --repair command to fix up the enref field. The --full option +** gives a full report showing the enref value on all entries in the +** emailblob table. +*/ +void test_refcheck_emailblob(void){ + int doRepair; + int fullReport; + Blob sql; + Stmt q; + int nErr = 0; + db_find_and_open_repository(0, 0); + fullReport = find_option("full",0,0)!=0; + doRepair = find_option("repair",0,0)!=0; + verify_all_options(); + if( !db_table_exists("repository","emailblob") ){ + fossil_print("emailblob table is not configured - nothing to check\n"); + return; + } + db_multi_exec( + "CREATE TEMP TABLE refcnt(id INTEGER PRIMARY KEY, n);" + "INSERT INTO refcnt SELECT ets, count(*) FROM (" + " SELECT ets FROM emailblob" + " UNION ALL" + " SELECT emsgid FROM emailbox" + " UNION ALL" + " SELECT emsgid FROM emailoutq" + ") WHERE ets IS NOT NULL GROUP BY 1;" + "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;" + ); + if( doRepair ){ + db_multi_exec( + "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)" + ); + } + blob_init(&sql, 0, 0); + blob_append_sql(&sql, + "SELECT a.emailid, a.enref, b.n" + " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id" + ); + if( !fullReport ){ + blob_append_sql(&sql, " WHERE a.enref!=b.n"); + } + db_prepare_blob(&q, &sql); + blob_reset(&sql); + while( db_step(&q)==SQLITE_ROW ){ + sqlite3_int64 id = db_column_int64(&q,0); + int n1 = db_column_int(&q, 1); + int n2 = db_column_int(&q, 2); + if( n1!=n2 ) nErr++; + fossil_print("%12lld %4d %4d%s\n", id, n1, n2, n1!=n2 ? " ERROR" : ""); + } + db_finalize(&q); + if( nErr ){ + fossil_print("Number of incorrect emailblob.enref values: %d\n",nErr); + } +} + /* ** COMMAND: smtpd ** ** Usage: %fossil smtpd [OPTIONS] REPOSITORY Index: src/stat.c ================================================================== --- src/stat.c +++ src/stat.c @@ -51,10 +51,70 @@ sqlite3_snprintf(nOut, zOut, "%.1fMB", (double)v/1000000.0); }else{ sqlite3_snprintf(nOut, zOut, "%.1fGB", (double)v/1000000000.0); } } + +/* +** Generate stats for the email notification subsystem. +*/ +void stats_for_email(void){ + const char *zDest = db_get("email-send-method",0); + int nSub, nASub, nPend, nDPend; + const char *zDir, *zDb, *zCmd, *zRelay; + @ Outgoing Email: + if( fossil_strcmp(zDest,"pipe")==0 + && (zCmd = db_get("email-send-command",0))!=0 + ){ + @ Piped to command "%h(zCmd)" + }else + if( fossil_strcmp(zDest,"db")==0 + && (zDb = db_get("email-send-db",0))!=0 + ){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc; + @ Queued to database "%h(zDb)" + rc = sqlite3_open(zDb, &db); + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM email",-1,&pStmt,0); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + @ (%,d(sqlite3_column_int(pStmt,0)) messages, + @ %,d(file_size(zDb,ExtFILE)) bytes) + } + sqlite3_finalize(pStmt); + } + sqlite3_close(db); + }else + if( fossil_strcmp(zDest,"dir")==0 + && (zDir = db_get("email-send-dir",0))!=0 + ){ + @ Written to files in "%h(zDir)" + @ (%,d(file_directory_size(zDir,0,1)) messages) + }else + if( fossil_strcmp(zDest,"relay")==0 + && (zRelay = db_get("email-send-relayhost",0))!=0 + ){ + @ Relay to %h(zRelay) using SMTP + } + else{ + @ Off + } + @ + nPend = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); + nDPend = db_int(0,"SELECT count(*) FROM pending_alert" + " WHERE NOT sentDigest"); + @ Pending Alerts: + @ %,d(nPend) normal, %,d(nDPend) digest + @ + @ Subscribers: + nSub = db_int(0, "SELECT count(*) FROM subscriber"); + nASub = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" + " AND NOT sdonotcall AND length(ssub)>1"); + @ %,d(nASub) active, %,d(nSub) total + @ +} /* ** WEBPAGE: stat ** ** Show statistics and global information about the repository. @@ -207,58 +267,11 @@ @ %h(g.zErrlog) (%,lld(szFile) bytes) } @ } if( g.perm.Admin && email_enabled() ){ - const char *zDest = db_get("email-send-method",0); - int nSub, nASub, nPend, nDPend; - const char *zDir, *zDb, *zCmd; - @ Outgoing Email: - if( fossil_strcmp(zDest,"pipe")==0 - && (zCmd = db_get("email-send-command",0))!=0 - ){ - @ Piped to command "%h(zCmd)" - }else - if( fossil_strcmp(zDest,"db")==0 - && (zDb = db_get("email-send-db",0))!=0 - ){ - sqlite3 *db; - sqlite3_stmt *pStmt; - int rc; - @ Queued to database "%h(zDb)" - rc = sqlite3_open(zDb, &db); - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM email",-1,&pStmt,0); - if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - @ (%,d(sqlite3_column_int(pStmt,0)) messages, - @ %,d(file_size(zDb,ExtFILE)) bytes) - } - sqlite3_finalize(pStmt); - } - sqlite3_close(db); - }else - if( fossil_strcmp(zDest,"dir")==0 - && (zDir = db_get("email-send-dir",0))!=0 - ){ - @ Written to files in "%h(zDir)" - @ (%,d(file_directory_size(zDir,0,1)) messages) - }else{ - @ Off - } - @ - nPend = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); - nDPend = db_int(0,"SELECT count(*) FROM pending_alert" - " WHERE NOT sentDigest"); - @ Pending Alerts: - @ %,d(nPend) normal, %,d(nDPend) digest - @ - @ Subscribers: - nSub = db_int(0, "SELECT count(*) FROM subscriber"); - nASub = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" - " AND NOT sdonotcall AND length(ssub)>1"); - @ %,d(nASub) active, %,d(nSub) total - @ + stats_for_email(); } @ style_footer(); } Index: src/webmail.c ================================================================== --- src/webmail.c +++ src/webmail.c @@ -352,26 +352,242 @@ } } emailtoc_free(p); blob_reset(&email); } + +/* +** Add the select/option box to the timeline submenu that shows +** the various email message formats. +*/ +static void webmail_f_submenu(void){ + static const char *az[] = { + "0", "Normal", + "1", "Decoded", + "2", "Raw", + }; + style_submenu_multichoice("f", sizeof(az)/(2*sizeof(az[0])), az, 0); +} + +/* +** If the first N characters of z[] are the name of a header field +** that should be shown in "Normal" mode, then return 1. +*/ +static int webmail_normal_header(const char *z, int N){ + static const char *az[] = { + "To", "Cc", "Bcc", "Date", "From", "Subject", + }; + int i; + for(i=0; i%h(db_column_text(&q, 0)) + }else{ + EmailToc *p = emailtoc_from_email(&msg); + int i, j; + @

+ for(i=0; inHdr; i++){ + char *z = p->azHdr[i]; + email_hdr_unfold(z); + for(j=0; z[j] && z[j]!=':'; j++){} + if( eFormat==0 && !webmail_normal_header(z, j) ) continue; + if( z[j]!=':' ){ + @ %h(z)
+ }else{ + z[j] = 0; + @ %h(z): %h(z+j+1)
+ } + } + for(i=0; inBody; i++){ + @


Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \ + if( p->aBody[i].zFilename ){ + @ "%h(p->aBody[i].zFilename)" + } + @ + if( eFormat==0 ){ + if( strncmp(p->aBody[i].zMimetype, "text/plain", 10)!=0 ) continue; + if( p->aBody[i].zFilename ) continue; + }else{ + if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue; + } + switch( p->aBody[i].encoding ){ + case EMAILENC_B64: { + int n = 0; + decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent); + break; + } + case EMAILENC_QUOTED: { + int n = 0; + decodeQuotedPrintable(p->aBody[i].zContent, &n); + break; + } + } + @
%h(p->aBody[i].zContent)
+ } + } + } + db_finalize(&q); + + if( eState==0 ){ + /* If is message is currently Unread, change it to Read */ + blob_append_sql(&sql, + "UPDATE emailbox SET estate=1 " + " WHERE estate=0 AND ebid=%d", + emailid + ); + if( zUser ) blob_append_sql(&sql, " AND euser=%Q", zUser); + db_multi_exec("%s", blob_sql_text(&sql)); + blob_reset(&sql); + eState = 1; + } + + url_add_parameter(pUrl, "id", 0); + sqlite3_snprintf(sizeof(zENum), zENum, "e%d", emailid); + if( eState==2 ){ + style_submenu_element("Undelete","%s", + url_render(pUrl,"read","1",zENum,"1")); + } + if( eState==1 ){ + style_submenu_element("Delete", "%s", + url_render(pUrl,"trash","1",zENum,"1")); + style_submenu_element("Mark As Unread", "%s", + url_render(pUrl,"unread","1",zENum,"1")); + } + if( eState==3 ){ + style_submenu_element("Delete", "%s", + url_render(pUrl,"trash","1",zENum,"1")); + } + + db_end_transaction(0); + style_footer(); + return; +} + +/* +** Scan the query parameters looking for parameters with name of the +** form "eN" where N is an integer. For all such integers, change +** the state of every emailbox entry with ebid==N to eStateNew provided +** that either zUser is NULL or matches. +** +** Or if eNewState==99, then delete the entries. +*/ +static void webmail_change_state(int eNewState, const char *zUser){ + Blob sql; + int sep = '('; + int i; + const char *zName; + int n; + if( !cgi_csrf_safe(0) ) return; + blob_init(&sql, 0, 0); + if( eNewState==99 ){ + blob_append_sql(&sql, "DELETE FROM emailbox WHERE estate==2 AND ebid IN "); + }else{ + blob_append_sql(&sql, "UPDATE emailbox SET estate=%d WHERE ebid IN ", + eNewState); + } + for(i=0; (zName = cgi_parameter_name(i))!=0; i++){ + if( zName[0]!='e' ) continue; + if( !fossil_isdigit(zName[1]) ) continue; + n = atoi(zName+1); + blob_append_sql(&sql, "%c%d", sep, n); + sep = ','; + } + if( zUser ){ + blob_append_sql(&sql, ") AND euser=%Q", zUser); + }else{ + blob_append_sql(&sql, ")"); + } + if( sep==',' ){ + db_multi_exec("%s", blob_sql_text(&sql)); + } + blob_reset(&sql); +} + + +/* +** Add the select/option box to the timeline submenu that shows +** which messages to include in the index. +*/ +static void webmail_d_submenu(void){ + static const char *az[] = { + "0", "InBox", + "1", "Unread", + "2", "Trash", + "3", "Sent", + "4", "Everything", + }; + style_submenu_multichoice("d", sizeof(az)/(2*sizeof(az[0])), az, 0); +} /* ** WEBPAGE: webmail ** ** This page can be used to read content from the EMAILBOX table ** that contains email received by the "fossil smtpd" command. +** +** Query parameters: +** +** id=N Show a single email entry emailbox.ebid==N +** f=N Display format. 0: decoded 1: raw +** user=USER Show mailbox for USER (admin only). +** user=* Show mailbox for all users (admin only). +** d=N 0: inbox+unread 1: unread-only 2: trash 3: all +** eN Select email entry emailbox.ebid==N +** trash Move selected entries to trash (estate=2) +** read Mark selected entries as read (estate=1) +** unread Mark selected entries as unread (estate=0) +** */ void webmail_page(void){ int emailid; Stmt q; Blob sql; int showAll = 0; const char *zUser = 0; + int d = 0; /* Display mode. 0..3. d= query parameter */ + int pg = 0; /* Page number */ + int N = 50; /* Results per page */ + int got; /* Number of results on this page */ + char zPPg[30]; /* Previous page */ + char zNPg[30]; /* Next page */ HQuery url; login_check_credentials(); - if( g.zLogin==0 ){ + if( !login_is_individual() ){ login_needed(0); return; } if( !db_table_exists("repository","emailbox") ){ style_header("Webmail Not Available"); @@ -389,116 +605,208 @@ if( fossil_strcmp(zUser,"*")==0 ){ showAll = 1; zUser = 0; } } + }else{ + zUser = g.zLogin; } + if( P("d") ) url_add_parameter(&url, "d", P("d")); if( emailid>0 ){ - style_submenu_element("Index", "%s", url_render(&url,"id",0,0,0)); - blob_init(&sql, 0, 0); - blob_append_sql(&sql, "SELECT decompress(etxt)" - " FROM emailblob WHERE emailid=%d", - emailid); - if( !g.perm.Admin ){ - blob_append_sql(&sql, " AND EXISTS(SELECT 1 FROM emailbox WHERE" - " euser=%Q AND emsgid=emailid)", g.zLogin); - } - db_prepare_blob(&q, &sql); - blob_reset(&sql); - if( db_step(&q)==SQLITE_ROW ){ - Blob msg = db_column_text_as_blob(&q, 0); - url_add_parameter(&url, "id", P("id")); - style_header("Message %d",emailid); - if( PB("raw") ){ - @
%h(db_column_text(&q, 0))
- style_submenu_element("Decoded", "%s", url_render(&url,"raw",0,0,0)); - }else{ - EmailToc *p = emailtoc_from_email(&msg); - int i, j; - style_submenu_element("Raw", "%s", url_render(&url,"raw","1",0,0)); - @

- for(i=0; inHdr; i++){ - char *z = p->azHdr[i]; - email_hdr_unfold(z); - for(j=0; z[j] && z[j]!=':'; j++){} - if( z[j]!=':' ){ - @ %h(z)
- }else{ - z[j] = 0; - @ %h(z): %h(z+j+1)
- } - } - for(i=0; inBody; i++){ - @


Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \ - if( p->aBody[i].zFilename ){ - @ "%h(p->aBody[i].zFilename)" - } - @ - if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue; - switch( p->aBody[i].encoding ){ - case EMAILENC_B64: { - int n = 0; - decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent); - break; - } - case EMAILENC_QUOTED: { - int n = 0; - decodeQuotedPrintable(p->aBody[i].zContent, &n); - break; - } - } - @
%h(p->aBody[i].zContent)
- } - } - style_footer(); - db_finalize(&q); - return; - } - db_finalize(&q); + webmail_show_one_message(&url, emailid, zUser); + return; } style_header("Webmail"); + webmail_d_submenu(); + db_begin_transaction(); + if( P("trash")!=0 ) webmail_change_state(2,zUser); + if( P("unread")!=0 ) webmail_change_state(0,zUser); + if( P("read")!=0 ) webmail_change_state(1,zUser); + if( P("purge")!=0 ) webmail_change_state(99,zUser); blob_init(&sql, 0, 0); blob_append_sql(&sql, - /* 0 1 2 3 4 5 */ - "SELECT efrom, datetime(edate,'unixepoch'), estate, esubject, emsgid, euser" + "CREATE TEMP TABLE tmbox AS " + "SELECT ebid," /* 0 */ + " efrom," /* 1 */ + " datetime(edate,'unixepoch')," /* 2 */ + " estate," /* 3 */ + " esubject," /* 4 */ + " euser" /* 5 */ " FROM emailbox" ); + d = atoi(PD("d","0")); + switch( d ){ + case 0: { /* Show unread and read */ + blob_append_sql(&sql, " WHERE estate<=1"); + break; + } + case 1: { /* Unread messages only */ + blob_append_sql(&sql, " WHERE estate=0"); + break; + } + case 2: { /* Trashcan only */ + blob_append_sql(&sql, " WHERE estate=2"); + break; + } + case 3: { /* Outgoing email only */ + blob_append_sql(&sql, " WHERE estate=3"); + break; + } + case 4: { /* Everything */ + blob_append_sql(&sql, " WHERE 1"); + break; + } + } if( showAll ){ style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); }else if( zUser!=0 ){ style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); if( fossil_strcmp(zUser, g.zLogin)!=0 ){ style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); } if( zUser ){ - blob_append_sql(&sql, " WHERE euser=%Q", zUser); + blob_append_sql(&sql, " AND euser=%Q", zUser); }else{ - blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin); + blob_append_sql(&sql, " AND euser=%Q", g.zLogin); } }else{ if( g.perm.Admin ){ style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); } - blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin); + blob_append_sql(&sql, " AND euser=%Q", g.zLogin); } - blob_append_sql(&sql, " ORDER BY edate DESC limit 50"); - db_prepare_blob(&q, &sql); + pg = atoi(PD("pg","0")); + blob_append_sql(&sql, " ORDER BY edate DESC limit %d offset %d", N+1, pg*N); + db_multi_exec("%s", blob_sql_text(&sql)); + got = db_int(0, "SELECT count(*) FROM tmbox"); + db_prepare(&q, "SELECT * FROM tmbox LIMIT %d", N); blob_reset(&sql); - @
    + @ + @ + @ + } + @
    + if( d==2 ){ + @ + @ + }else{ + @ + if( d!=1 ){ + @ + } + @ + } + @ refresh + @ + if( pg>0 ){ + sqlite3_snprintf(sizeof(zPPg), zPPg, "%d", pg-1); + @ < Newer   + } + if( got>50 ){ + sqlite3_snprintf(sizeof(zNPg),zNPg,"%d",pg+1); + @ Older >
    + @ while( db_step(&q)==SQLITE_ROW ){ - const char *zId = db_column_text(&q,4); - const char *zFrom = db_column_text(&q, 0); - const char *zDate = db_column_text(&q, 1); - const char *zSubject = db_column_text(&q, 3); - @
  1. + const char *zId = db_column_text(&q,0); + const char *zFrom = db_column_text(&q, 1); + const char *zDate = db_column_text(&q, 2); + const char *zSubject = db_column_text(&q, 4); + @
  2. + @ + @ + @ if( showAll ){ const char *zTo = db_column_text(&q,5); - @ %h(zTo): + @ } - @ \ - @ %h(zFrom) → %h(zSubject) - @ %h(zDate) + @ } db_finalize(&q); - @ - style_footer(); + @
    %h(zFrom)%h(zSubject) \ + @ %s(zDate)%h(zTo)
    + @ + style_footer(); + db_end_transaction(0); +} + +/* +** WEBPAGE: test-emailblob +** +** This page, accessible only to administrators, allows easy viewing of +** the emailblob table - the table that contains the text of email messages +** both inbound and outbound, and transcripts of SMTP sessions. +** +** id=N Show the text of emailblob with emailid==N +** +*/ +void webmail_emailblob_page(void){ + int id = atoi(PD("id","0")); + Stmt q; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + add_content_sql_commands(g.db); + style_header("emailblob table"); + if( id>0 ){ + style_submenu_element("Index", "%R/test-emailblob"); + @
      + db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id); + while( db_step(&q)==SQLITE_ROW ){ + int id = db_column_int(&q, 0); + @
    • emailblob entry %d(id) + } + db_finalize(&q); + db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUser = db_column_text(&q, 0); + int e = db_column_int(&q, 1); + @
    • emailbox for %h(zUser) state %d(e) + } + db_finalize(&q); + db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE emsgid=%d", id); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFrom = db_column_text(&q, 0); + const char *zTo = db_column_text(&q, 1); + @
    • emailoutq message body from %h(zFrom) to %h(zTo) + } + db_finalize(&q); + db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE ets=%d", id); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFrom = db_column_text(&q, 0); + const char *zTo = db_column_text(&q, 1); + @
    • emailoutq transcript from %h(zFrom) to %h(zTo) + } + db_finalize(&q); + @
    + @
    + db_prepare(&q, "SELECT decompress(etxt) FROM emailblob WHERE emailid=%d", + id); + while( db_step(&q)==SQLITE_ROW ){ + const char *zContent = db_column_text(&q, 0); + @
    %h(zContent)
    + } + db_finalize(&q); + }else{ + db_prepare(&q, + "SELECT emailid, enref, ets, datetime(etime,'unixepoch')" + " FROM emailblob ORDER BY etime DESC, emailid DESC"); + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + int id = db_column_int(&q, 0); + int nref = db_column_int(&q, 1); + int ets = db_column_int(&q, 2); + const char *zDate = db_column_text(&q, 3); + @ + @ + @ + @ + @ + } + @
    emailid enref ets etime
    %d(id) + @ %d(nref)%d(ets)%h(zDate)
    + db_finalize(&q); + } + style_footer(); }