Fossil

Check-in [264223fc]
Login

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

Overview
Comment:Use the email content parser to the prototype webmail page.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:264223fc5981cd27b255e5f4170c2a81c9a24e69622bb6dc29c42c892e7087b2
User & Date: drh 2018-07-13 15:07:13
Context
2018-07-13
16:06
When rendering SQLite log messages to the error log, include the SQL for all busy SQL statements in the log message. check-in: c6ecf21f user: drh tags: trunk
15:07
Use the email content parser to the prototype webmail page. check-in: 264223fc user: drh tags: trunk
10:04
Append -ldl only when needed on the target platform; OpenBSD resolves it from the standard libc. check-in: 7cdb522b user: ashepilko tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/db.c.

   517    517   }
   518    518   char *db_column_malloc(Stmt *pStmt, int N){
   519    519     return mprintf("%s", db_column_text(pStmt, N));
   520    520   }
   521    521   void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
   522    522     blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
   523    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;
   524    530   }
   525    531   
   526    532   /*
   527    533   ** Initialize a blob to an ephemeral copy of the content of a
   528    534   ** column in the current row.  The data in the blob will become
   529    535   ** invalid when the statement is stepped or reset.
   530    536   */

Changes to src/smtp.c.

   650    650   @ CREATE TABLE IF NOT EXISTS repository.emailbox(
   651    651   @   euser TEXT,          -- User who received this email
   652    652   @   edate INT,           -- Date received.  Seconds since 1970
   653    653   @   efrom TEXT,          -- Who is the email from
   654    654   @   emsgid INT,          -- Raw email text
   655    655   @   ets INT,             -- Transcript of the receiving SMTP session
   656    656   @   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
   658    659   @ );
   659    660   @
   660    661   @ -- Information on how to deliver incoming email.
   661    662   @ CREATE TABLE IF NOT EXISTS repository.emailroute(
   662    663   @   eaddr TEXT PRIMARY KEY,  -- Email address
   663    664   @   epolicy TEXT             -- How to handle email sent to this address
   664    665   @ ) WITHOUT ROWID;
................................................................................
   681    682   */
   682    683   static const char zEmailDrop[] =
   683    684   @ DROP TABLE IF EXISTS emailblob;
   684    685   @ DROP TABLE IF EXISTS emailbox;
   685    686   @ DROP TABLE IF EXISTS emailroute;
   686    687   @ DROP TABLE IF EXISTS emailqueue;
   687    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  +
   688    699   
   689    700   /*
   690    701   ** Populate the schema of a database.
   691    702   **
   692    703   **   eForce==0          Fast
   693    704   **   eForce==1          Run CREATE TABLE statements every time
   694    705   **   eForce==2          DROP then rerun CREATE TABLE

Changes to src/webmail.c.

   115    115   */
   116    116   static int email_line_length(const char *z){
   117    117     int i;
   118    118     for(i=0; z[i] && (z[i]!='\n' || z[i+1]==' ' || z[i+1]=='\t'); i++){}
   119    119     if( z[i]=='\n' ) i++;
   120    120     return i;
   121    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  +}
   122    145   
   123    146   /*
   124    147   ** Return a pointer to the first non-whitespace character in z
   125    148   */
   126    149   static const char *firstToken(const char *z){
   127    150     while( fossil_isspace(*z) ){
   128    151       z++;
................................................................................
   176    199                                    /*  123456789 123456 */
   177    200         }else if( sqlite3_strnicmp(z2, "quoted-printable", 16)==0 ){
   178    201           pBody->encoding = EMAILENC_QUOTED;
   179    202         }else{
   180    203           pBody->encoding = EMAILENC_NONE;
   181    204         }
   182    205       }
   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  +    }
   184    213       i += n;
   185    214     }
   186    215     if( multipartBody ){
   187    216       p->nBody--;
   188    217       emailtoc_add_multipart(p, z+i);
   189    218     }else{
   190    219       pBody->zContent = z+i;
................................................................................
   202    231     EmailToc *p,          /* Append the segments here */
   203    232     char *z               /* The body component.  zero-terminated */
   204    233   ){
   205    234     int nB;               /* Size of the boundary string */
   206    235     int iStart;           /* Start of the coding region past boundary mark */
   207    236     int i;                /* Loop index */
   208    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  +  }
   209    246   
   210    247     /* Find the length of the boundary mark. */
   211         -  while( fossil_isspace(z[0]) ) z++;
   212    248     zBoundary = z;
   213    249     for(nB=0; z[nB] && !fossil_isspace(z[nB]); nB++){}
   214    250     if( nB==0 ) return;
          251  +
   215    252     z += nB;
   216    253     while( fossil_isspace(z[0]) ) z++;
   217    254     zBoundary[nB] = 0;
   218    255     for(i=iStart=0; z[i]; i++){
   219    256       if( z[i]=='\n' && strncmp(z+i+1, zBoundary, nB)==0 ){
   220    257         z[i+1] = 0;
   221    258         emailtoc_add_multipart_segment(p, z+iStart, 0);
................................................................................
   222    259         iStart = i+nB;
   223    260         if( z[iStart]=='-' && z[iStart+1]=='-' ) return;
   224    261         while( fossil_isspace(z[iStart]) ) iStart++;
   225    262         i = iStart;
   226    263       }
   227    264     }
   228    265   }
   229         -
   230    266   
   231    267   /*
   232    268   ** Compute a table-of-contents (EmailToc) for the email message
   233    269   ** provided on the input.
   234    270   **
   235    271   ** This routine will cause pEmail to become zero-terminated if it is
   236    272   ** not already.  It will also insert zero characters into parts of
................................................................................
   240    276     char *z;
   241    277     EmailToc *p = emailtoc_alloc();
   242    278     blob_terminate(pEmail);
   243    279     z = blob_buffer(pEmail);
   244    280     emailtoc_add_multipart_segment(p, z, 1);
   245    281     return p;
   246    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  +}
   247    302   
   248    303   /*
   249    304   ** COMMAND: test-decode-email
   250    305   **
   251    306   ** Usage: %fossil test-decode-email FILE
   252    307   **
   253    308   ** Read an rfc-2822 formatted email out of FILE, then write a decoding
................................................................................
   260    315     verify_all_options();
   261    316     if( g.argc!=3 ) usage("FILE");
   262    317     blob_read_from_file(&email, g.argv[2], ExtFILE);
   263    318     p = emailtoc_from_email(&email);
   264    319     fossil_print("%d header line and %d content segments\n",
   265    320                  p->nHdr, p->nBody);
   266    321     for(i=0; i<p->nHdr; i++){
          322  +    email_hdr_unfold(p->azHdr[i]);
   267    323       fossil_print("%3d: %s\n", i, p->azHdr[i]);
   268    324     }
   269    325     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",
   271    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;
   272    333       switch( p->aBody[i].encoding ){
   273    334         case EMAILENC_B64: {
   274    335           int n = 0;
   275    336           decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent);
   276    337           fossil_print("%s", p->aBody[i].zContent);
   277    338           if( n && p->aBody[i].zContent[n-1]!='\n' ) fossil_print("\n");
   278    339           break;
................................................................................
   301    362   ** that contains email received by the "fossil smtpd" command.
   302    363   */
   303    364   void webmail_page(void){
   304    365     int emailid;
   305    366     Stmt q;
   306    367     Blob sql;
   307    368     int showAll = 0;
          369  +  const char *zUser = 0;
          370  +  HQuery url;
   308    371     login_check_credentials();
   309    372     if( g.zLogin==0 ){
   310    373       login_needed(0);
   311    374       return;
   312    375     }
   313    376     if( !db_table_exists("repository","emailbox") ){
   314    377       style_header("Webmail Not Available");
   315    378       @ <p>This repository is not configured to provide webmail</p>
   316    379       style_footer();
   317    380       return;
   318    381     }
   319    382     add_content_sql_commands(g.db);
   320    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  +  }
   321    395     if( emailid>0 ){
          396  +    style_submenu_element("Index", "%s", url_render(&url,"id",0,0,0));
   322    397       blob_init(&sql, 0, 0);
   323    398       blob_append_sql(&sql, "SELECT decompress(etxt)"
   324    399                             " FROM emailblob WHERE emailid=%d",
   325    400                             emailid);
   326    401       if( !g.perm.Admin ){
   327    402         blob_append_sql(&sql, " AND EXISTS(SELECT 1 FROM emailbox WHERE"
   328    403                               " euser=%Q AND emsgid=emailid)", g.zLogin);
   329    404       }
   330    405       db_prepare_blob(&q, &sql);
   331    406       blob_reset(&sql);
   332    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"));
   333    410         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  +      }      
   335    452         style_footer();
   336    453         db_finalize(&q);
   337    454         return;
   338    455       }
   339    456       db_finalize(&q);
   340    457     }
   341    458     style_header("Webmail");
   342    459     blob_init(&sql, 0, 0);
   343    460     blob_append_sql(&sql,
   344    461           /*    0       1                           2        3        4      5 */
   345    462       "SELECT efrom, datetime(edate,'unixepoch'), estate, esubject, emsgid, euser"
   346    463       " FROM emailbox"
   347    464     );
   348         -  if( g.perm.Admin ){
   349         -    const char *zUser = P("user");
   350         -    if( P("all")!=0 ){
   351         -      /* Show all email messages */
   352         -      showAll = 1;
          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);
   353    474       }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         -      }
          475  +      blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
   360    476       }
   361    477     }else{
          478  +    if( g.perm.Admin ){
          479  +      style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0));
          480  +    }
   362    481       blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin);
   363    482     }
   364    483     blob_append_sql(&sql, " ORDER BY edate DESC limit 50");
   365    484     db_prepare_blob(&q, &sql);
   366    485     blob_reset(&sql);
   367    486     @ <ol>
   368    487     while( db_step(&q)==SQLITE_ROW ){
   369         -    int emailid = db_column_int(&q,4);
          488  +    char *zId = db_column_text(&q,4);
   370    489       const char *zFrom = db_column_text(&q, 0);
   371    490       const char *zDate = db_column_text(&q, 1);
   372    491       const char *zSubject = db_column_text(&q, 3);
   373    492       @ <li>
   374    493       if( showAll ){
   375    494         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>:
   377    496       }
   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>
   379    499       @ %h(zDate)
   380    500     }
   381    501     db_finalize(&q);
   382    502     @ </ol>
   383    503     style_footer(); 
   384    504   }