Fossil SCM
Finish off webmail delete
Commit
274df1cad42f304befd5a0926c937c2e2bb322af54a985e397f180b37ad13ff3
Parent
db5c2d399c143a0…
1 file changed
-921
D
src/webmail.c
-921
| --- a/src/webmail.c | ||
| +++ b/src/webmail.c | ||
| @@ -1,921 +0,0 @@ | ||
| 1 | -/* | |
| 2 | -** Copyright (c) 2018 D. Richard Hipp | |
| 3 | -** | |
| 4 | -** This program is free software; you can redistribute it and/or | |
| 5 | -** modify it under the terms of the Simplified BSD License (also | |
| 6 | -** known as the "2-Clause License" or "FreeBSD License".) | |
| 7 | -** | |
| 8 | -** This program is distributed in the hope that it will be useful, | |
| 9 | -** but without any warranty; without even the implied warranty of | |
| 10 | -** merchantability or fitness for a particular purpose. | |
| 11 | -** | |
| 12 | -** Author contact information: | |
| 13 | -** [email protected] | |
| 14 | -** http://www.hwaci.com/drh/ | |
| 15 | -** | |
| 16 | -******************************************************************************* | |
| 17 | -** | |
| 18 | -** Implementation of web pages for managing the email storage tables | |
| 19 | -** (if they exist): | |
| 20 | -** | |
| 21 | -** emailbox | |
| 22 | -** emailblob | |
| 23 | -** emailroute | |
| 24 | -*/ | |
| 25 | -#include "config.h" | |
| 26 | -#include "webmail.h" | |
| 27 | -#include <assert.h> | |
| 28 | - | |
| 29 | - | |
| 30 | -#if INTERFACE | |
| 31 | - | |
| 32 | -/* Recognized content encodings */ | |
| 33 | -#define EMAILENC_NONE 0 /* No encoding */ | |
| 34 | -#define EMAILENC_B64 1 /* Base64 encoded */ | |
| 35 | -#define EMAILENC_QUOTED 2 /* Quoted printable */ | |
| 36 | - | |
| 37 | -/* An instance of the following object records the location of important | |
| 38 | -** attributes on a single element in a multipart email message body. | |
| 39 | -*/ | |
| 40 | -struct EmailBody { | |
| 41 | - char zMimetype[32]; /* Mimetype */ | |
| 42 | - u8 encoding; /* Type of encoding */ | |
| 43 | - char *zFilename; /* From content-disposition: */ | |
| 44 | - char *zContent; /* Content. \0 terminator inserted */ | |
| 45 | -}; | |
| 46 | - | |
| 47 | -/* | |
| 48 | -** An instance of the following object describes the struture of | |
| 49 | -** an rfc-2822 email message. | |
| 50 | -*/ | |
| 51 | -struct EmailToc { | |
| 52 | - int nHdr; /* Number of header lines */ | |
| 53 | - int nHdrAlloc; /* Number of header lines allocated */ | |
| 54 | - char **azHdr; /* Pointer to header line. \0 terminator inserted */ | |
| 55 | - int nBody; /* Number of body segments */ | |
| 56 | - int nBodyAlloc; /* Number of body segments allocated */ | |
| 57 | - EmailBody *aBody; /* Location of body information */ | |
| 58 | -}; | |
| 59 | -#endif | |
| 60 | - | |
| 61 | -/* | |
| 62 | -** Free An EmailToc object | |
| 63 | -*/ | |
| 64 | -void emailtoc_free(EmailToc *p){ | |
| 65 | - int i; | |
| 66 | - fossil_free(p->azHdr); | |
| 67 | - for(i=0; i<p->nBody; i++){ | |
| 68 | - fossil_free(p->aBody[i].zFilename); | |
| 69 | - } | |
| 70 | - fossil_free(p->aBody); | |
| 71 | - fossil_free(p); | |
| 72 | -} | |
| 73 | - | |
| 74 | -/* | |
| 75 | -** Allocate a new EmailToc object | |
| 76 | -*/ | |
| 77 | -EmailToc *emailtoc_alloc(void){ | |
| 78 | - EmailToc *p = fossil_malloc( sizeof(*p) ); | |
| 79 | - memset(p, 0, sizeof(*p)); | |
| 80 | - return p; | |
| 81 | -} | |
| 82 | - | |
| 83 | -/* | |
| 84 | -** Add a new body element to an EmailToc. | |
| 85 | -*/ | |
| 86 | -EmailBody *emailtoc_new_body(EmailToc *p){ | |
| 87 | - EmailBody *pNew; | |
| 88 | - p->nBody++; | |
| 89 | - if( p->nBody>p->nBodyAlloc ){ | |
| 90 | - p->nBodyAlloc = (p->nBodyAlloc+1)*2; | |
| 91 | - p->aBody = fossil_realloc(p->aBody, sizeof(p->aBody[0])*p->nBodyAlloc); | |
| 92 | - } | |
| 93 | - pNew = &p->aBody[p->nBody-1]; | |
| 94 | - memset(pNew, 0, sizeof(*pNew)); | |
| 95 | - return pNew; | |
| 96 | -} | |
| 97 | - | |
| 98 | -/* | |
| 99 | -** Add a new header line to the EmailToc. | |
| 100 | -*/ | |
| 101 | -void emailtoc_new_header_line(EmailToc *p, char *z){ | |
| 102 | - p->nHdr++; | |
| 103 | - if( p->nHdr>p->nHdrAlloc ){ | |
| 104 | - p->nHdrAlloc = (p->nHdrAlloc+1)*2; | |
| 105 | - p->azHdr = fossil_realloc(p->azHdr, sizeof(p->azHdr[0])*p->nHdrAlloc); | |
| 106 | - } | |
| 107 | - p->azHdr[p->nHdr-1] = z; | |
| 108 | -} | |
| 109 | - | |
| 110 | -/* | |
| 111 | -** Return the length of a line in an email header. Continuation lines | |
| 112 | -** are included. Hence, this routine returns the number of bytes up to | |
| 113 | -** and including the first \n character that is followed by something | |
| 114 | -** other than whitespace. | |
| 115 | -*/ | |
| 116 | -static int email_line_length(const char *z){ | |
| 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){ | |
| 150 | - while( fossil_isspace(*z) ){ | |
| 151 | - z++; | |
| 152 | - } | |
| 153 | - return z; | |
| 154 | -} | |
| 155 | - | |
| 156 | -/* | |
| 157 | -** The n-bytes of content in z is a single multipart mime segment | |
| 158 | -** with its own header and body. Decode this one segment and add it to p; | |
| 159 | -** | |
| 160 | -** Rows of the header of the segment are added to p if bAddHeader is | |
| 161 | -** true. | |
| 162 | -*/ | |
| 163 | -LOCAL void emailtoc_add_multipart_segment( | |
| 164 | - EmailToc *p, /* Append the segments here */ | |
| 165 | - char *z, /* The body component */ | |
| 166 | - int bAddHeader /* True to add header lines to p */ | |
| 167 | -){ | |
| 168 | - int i, j; | |
| 169 | - int n; | |
| 170 | - int multipartBody = 0; | |
| 171 | - EmailBody *pBody = emailtoc_new_body(p); | |
| 172 | - i = 0; | |
| 173 | - while( z[i] ){ | |
| 174 | - n = email_line_length(&z[i]); | |
| 175 | - if( (n==2 && z[i]=='\r' && z[i+1]=='\n') || z[i]=='\n' || n==0 ){ | |
| 176 | - /* This is the blank line at the end of the header */ | |
| 177 | - i += n; | |
| 178 | - break; | |
| 179 | - } | |
| 180 | - for(j=i+n; j>i && fossil_isspace(z[j-1]); j--){} | |
| 181 | - z[j] = 0; | |
| 182 | - if( sqlite3_strnicmp(z+i, "Content-Type:", 13)==0 ){ | |
| 183 | - const char *z2 = firstToken(z+i+13); | |
| 184 | - if( z2 && strncmp(z2, "multipart/", 10)==0 ){ | |
| 185 | - multipartBody = 1; | |
| 186 | - }else{ | |
| 187 | - int j; | |
| 188 | - for(j=0; z2[j]=='/' || fossil_isalnum(z2[j]); j++){} | |
| 189 | - if( j>=sizeof(pBody->zMimetype) ) j = sizeof(pBody->zMimetype); | |
| 190 | - memcpy(pBody->zMimetype, z2, j); | |
| 191 | - pBody->zMimetype[j] = 0; | |
| 192 | - } | |
| 193 | - } | |
| 194 | - /* 123456789 123456789 123456 */ | |
| 195 | - if( sqlite3_strnicmp(z+i, "Content-Transfer-Encoding:", 26)==0 ){ | |
| 196 | - const char *z2 = firstToken(z+(i+26)); | |
| 197 | - if( z2 && sqlite3_strnicmp(z2, "base64", 6)==0 ){ | |
| 198 | - pBody->encoding = EMAILENC_B64; | |
| 199 | - /* 123456789 123456 */ | |
| 200 | - }else if( sqlite3_strnicmp(z2, "quoted-printable", 16)==0 ){ | |
| 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); | |
| 218 | - }else{ | |
| 219 | - pBody->zContent = z+i; | |
| 220 | - } | |
| 221 | -} | |
| 222 | - | |
| 223 | -/* | |
| 224 | -** The n-bytes of content in z are a multipart/ body component for | |
| 225 | -** an email message. Decode this into its individual segments. | |
| 226 | -** | |
| 227 | -** The component should start and end with a boundary line. There | |
| 228 | -** may be additional boundary lines in the middle. | |
| 229 | -*/ | |
| 230 | -LOCAL void emailtoc_add_multipart( | |
| 231 | - EmailToc *p, /* Append the segments here */ | |
| 232 | - char *z /* The body component. zero-terminated */ | |
| 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 ){ | |
| 257 | - z[i+1] = 0; | |
| 258 | - emailtoc_add_multipart_segment(p, z+iStart, 0); | |
| 259 | - iStart = i+nB; | |
| 260 | - if( z[iStart]=='-' && z[iStart+1]=='-' ) return; | |
| 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 | -** | |
| 271 | -** This routine will cause pEmail to become zero-terminated if it is | |
| 272 | -** not already. It will also insert zero characters into parts of | |
| 273 | -** the message, to delimit the various components. | |
| 274 | -*/ | |
| 275 | -EmailToc *emailtoc_from_email(Blob *pEmail){ | |
| 276 | - char *z; | |
| 277 | - EmailToc *p = emailtoc_alloc(); | |
| 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 | |
| 307 | -** | |
| 308 | -** Read an rfc-2822 formatted email out of FILE, then write a decoding | |
| 309 | -** to stdout. Use for testing and validating the email decoder. | |
| 310 | -*/ | |
| 311 | -void test_email_decode_cmd(void){ | |
| 312 | - Blob email; | |
| 313 | - EmailToc *p; | |
| 314 | - int i; | |
| 315 | - verify_all_options(); | |
| 316 | - if( g.argc!=3 ) usage("FILE"); | |
| 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); | |
| 338 | - if( n && p->aBody[i].zContent[n-1]!='\n' ) fossil_print("\n"); | |
| 339 | - break; | |
| 340 | - } | |
| 341 | - case EMAILENC_QUOTED: { | |
| 342 | - int n = 0; | |
| 343 | - decodeQuotedPrintable(p->aBody[i].zContent, &n); | |
| 344 | - fossil_print("%s", p->aBody[i].zContent); | |
| 345 | - if( n && p->aBody[i].zContent[n-1]!='\n' ) fossil_print("\n"); | |
| 346 | - break; | |
| 347 | - } | |
| 348 | - default: { | |
| 349 | - fossil_print("%s\n", p->aBody[i].zContent); | |
| 350 | - break; | |
| 351 | - } | |
| 352 | - } | |
| 353 | - } | |
| 354 | - emailtoc_free(p); | |
| 355 | - blob_reset(&email); | |
| 356 | -} | |
| 357 | - | |
| 358 | -/* | |
| 359 | -** Add the select/option box to the timeline submenu that shows | |
| 360 | -** the various email message formats. | |
| 361 | -*/ | |
| 362 | -static void webmail_f_submenu(void){ | |
| 363 | - static const char *const az[] = { | |
| 364 | - "0", "Normal", | |
| 365 | - "1", "Decoded", | |
| 366 | - "2", "Raw", | |
| 367 | - }; | |
| 368 | - style_submenu_multichoice("f", sizeof(az)/(2*sizeof(az[0])), az, 0); | |
| 369 | -} | |
| 370 | - | |
| 371 | -/* | |
| 372 | -** If the first N characters of z[] are the name of a header field | |
| 373 | -** that should be shown in "Normal" mode, then return 1. | |
| 374 | -*/ | |
| 375 | -static int webmail_normal_header(const char *z, int N){ | |
| 376 | - static const char *const az[] = { | |
| 377 | - "To", "Cc", "Bcc", "Date", "From", "Subject", | |
| 378 | - }; | |
| 379 | - int i; | |
| 380 | - for(i=0; i<sizeof(az)/sizeof(az[0]); i++){ | |
| 381 | - if( sqlite3_strnicmp(z, az[i], N)==0 ) return 1; | |
| 382 | - } | |
| 383 | - return 0; | |
| 384 | -} | |
| 385 | - | |
| 386 | -/* | |
| 387 | -** Paint a page showing a single email message | |
| 388 | -*/ | |
| 389 | -static void webmail_show_one_message( | |
| 390 | - HQuery *pUrl, /* Calling context */ | |
| 391 | - int emailid, /* emailbox.ebid to display */ | |
| 392 | - const char *zUser /* User who owns it, or NULL if does not matter */ | |
| 393 | -){ | |
| 394 | - Blob sql; | |
| 395 | - Stmt q; | |
| 396 | - int eState = -1; | |
| 397 | - int eTranscript = 0; | |
| 398 | - char zENum[30]; | |
| 399 | - style_submenu_element("Index", "%s", url_render(pUrl,"id",0,0,0)); | |
| 400 | - webmail_f_submenu(); | |
| 401 | - blob_init(&sql, 0, 0); | |
| 402 | - db_begin_transaction(); | |
| 403 | - blob_append_sql(&sql, | |
| 404 | - "SELECT decompress(etxt), estate, emailblob.ets" | |
| 405 | - " FROM emailblob, emailbox" | |
| 406 | - " WHERE emailid=emsgid AND ebid=%d", | |
| 407 | - emailid | |
| 408 | - ); | |
| 409 | - if( zUser ) blob_append_sql(&sql, " AND euser=%Q", zUser); | |
| 410 | - db_prepare_blob(&q, &sql); | |
| 411 | - blob_reset(&sql); | |
| 412 | - style_set_current_feature("webmail"); | |
| 413 | - style_header("Message %d",emailid); | |
| 414 | - if( db_step(&q)==SQLITE_ROW ){ | |
| 415 | - Blob msg = db_column_text_as_blob(&q, 0); | |
| 416 | - int eFormat = atoi(PD("f","0")); | |
| 417 | - eState = db_column_int(&q, 1); | |
| 418 | - eTranscript = db_column_int(&q, 2); | |
| 419 | - if( eFormat==2 ){ | |
| 420 | - @ <pre>%h(db_column_text(&q, 0))</pre> | |
| 421 | - }else{ | |
| 422 | - EmailToc *p = emailtoc_from_email(&msg); | |
| 423 | - int i, j; | |
| 424 | - @ <p> | |
| 425 | - for(i=0; i<p->nHdr; i++){ | |
| 426 | - char *z = p->azHdr[i]; | |
| 427 | - email_hdr_unfold(z); | |
| 428 | - for(j=0; z[j] && z[j]!=':'; j++){} | |
| 429 | - if( eFormat==0 && !webmail_normal_header(z, j) ) continue; | |
| 430 | - if( z[j]!=':' ){ | |
| 431 | - @ %h(z)<br> | |
| 432 | - }else{ | |
| 433 | - z[j] = 0; | |
| 434 | - @ <b>%h(z):</b> %h(z+j+1)<br> | |
| 435 | - } | |
| 436 | - } | |
| 437 | - for(i=0; i<p->nBody; i++){ | |
| 438 | - @ <hr><b>Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \ | |
| 439 | - if( p->aBody[i].zFilename ){ | |
| 440 | - @ "%h(p->aBody[i].zFilename)" | |
| 441 | - } | |
| 442 | - @ </b> | |
| 443 | - if( eFormat==0 ){ | |
| 444 | - if( strncmp(p->aBody[i].zMimetype, "text/plain", 10)!=0 ) continue; | |
| 445 | - if( p->aBody[i].zFilename ) continue; | |
| 446 | - }else{ | |
| 447 | - if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue; | |
| 448 | - } | |
| 449 | - switch( p->aBody[i].encoding ){ | |
| 450 | - case EMAILENC_B64: { | |
| 451 | - int n = 0; | |
| 452 | - decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent); | |
| 453 | - break; | |
| 454 | - } | |
| 455 | - case EMAILENC_QUOTED: { | |
| 456 | - int n = 0; | |
| 457 | - decodeQuotedPrintable(p->aBody[i].zContent, &n); | |
| 458 | - break; | |
| 459 | - } | |
| 460 | - } | |
| 461 | - @ <pre>%h(p->aBody[i].zContent)</pre> | |
| 462 | - } | |
| 463 | - } | |
| 464 | - } | |
| 465 | - db_finalize(&q); | |
| 466 | - | |
| 467 | - /* Optionally show the SMTP transcript */ | |
| 468 | - if( eTranscript>0 | |
| 469 | - && db_exists("SELECT 1 FROM emailblob WHERE emailid=%d", eTranscript) | |
| 470 | - ){ | |
| 471 | - if( P("ts")==0 ){ | |
| 472 | - sqlite3_snprintf(sizeof(zENum), zENum, "%d", emailid); | |
| 473 | - style_submenu_element("SMTP Transcript","%s", | |
| 474 | - url_render(pUrl, "ts", "1", "id", zENum)); | |
| 475 | - }else{ | |
| 476 | - db_prepare(&q, | |
| 477 | - "SELECT decompress(etxt) FROM emailblob WHERE emailid=%d", eTranscript | |
| 478 | - ); | |
| 479 | - if( db_step(&q)==SQLITE_ROW ){ | |
| 480 | - const char *zTranscript = db_column_text(&q, 0); | |
| 481 | - @ <hr> | |
| 482 | - @ <pre>%h(zTranscript)</pre> | |
| 483 | - } | |
| 484 | - db_finalize(&q); | |
| 485 | - } | |
| 486 | - } | |
| 487 | - | |
| 488 | - if( eState==0 ){ | |
| 489 | - /* If is message is currently Unread, change it to Read */ | |
| 490 | - blob_append_sql(&sql, | |
| 491 | - "UPDATE emailbox SET estate=1 " | |
| 492 | - " WHERE estate=0 AND ebid=%d", | |
| 493 | - emailid | |
| 494 | - ); | |
| 495 | - if( zUser ) blob_append_sql(&sql, " AND euser=%Q", zUser); | |
| 496 | - db_multi_exec("%s", blob_sql_text(&sql)); | |
| 497 | - blob_reset(&sql); | |
| 498 | - eState = 1; | |
| 499 | - } | |
| 500 | - | |
| 501 | - url_add_parameter(pUrl, "id", 0); | |
| 502 | - sqlite3_snprintf(sizeof(zENum), zENum, "e%d", emailid); | |
| 503 | - if( eState==2 ){ | |
| 504 | - style_submenu_element("Undelete","%s", | |
| 505 | - url_render(pUrl,"read","1",zENum,"1")); | |
| 506 | - } | |
| 507 | - if( eState==1 ){ | |
| 508 | - style_submenu_element("Delete", "%s", | |
| 509 | - url_render(pUrl,"trash","1",zENum,"1")); | |
| 510 | - style_submenu_element("Mark As Unread", "%s", | |
| 511 | - url_render(pUrl,"unread","1",zENum,"1")); | |
| 512 | - } | |
| 513 | - if( eState==3 ){ | |
| 514 | - style_submenu_element("Delete", "%s", | |
| 515 | - url_render(pUrl,"trash","1",zENum,"1")); | |
| 516 | - } | |
| 517 | - | |
| 518 | - db_end_transaction(0); | |
| 519 | - style_finish_page(); | |
| 520 | - return; | |
| 521 | -} | |
| 522 | - | |
| 523 | -/* | |
| 524 | -** Scan the query parameters looking for parameters with name of the | |
| 525 | -** form "eN" where N is an integer. For all such integers, change | |
| 526 | -** the state of every emailbox entry with ebid==N to eStateNew provided | |
| 527 | -** that either zUser is NULL or matches. | |
| 528 | -** | |
| 529 | -** Or if eNewState==99, then delete the entries. | |
| 530 | -*/ | |
| 531 | -static void webmail_change_state(int eNewState, const char *zUser){ | |
| 532 | - Blob sql; | |
| 533 | - int sep = '('; | |
| 534 | - int i; | |
| 535 | - const char *zName; | |
| 536 | - int n; | |
| 537 | - if( !cgi_csrf_safe(0) ) return; | |
| 538 | - blob_init(&sql, 0, 0); | |
| 539 | - if( eNewState==99 ){ | |
| 540 | - blob_append_sql(&sql, "DELETE FROM emailbox WHERE estate==2 AND ebid IN "); | |
| 541 | - }else{ | |
| 542 | - blob_append_sql(&sql, "UPDATE emailbox SET estate=%d WHERE ebid IN ", | |
| 543 | - eNewState); | |
| 544 | - } | |
| 545 | - for(i=0; (zName = cgi_parameter_name(i))!=0; i++){ | |
| 546 | - if( zName[0]!='e' ) continue; | |
| 547 | - if( !fossil_isdigit(zName[1]) ) continue; | |
| 548 | - n = atoi(zName+1); | |
| 549 | - blob_append_sql(&sql, "%c%d", sep, n); | |
| 550 | - sep = ','; | |
| 551 | - } | |
| 552 | - if( zUser ){ | |
| 553 | - blob_append_sql(&sql, ") AND euser=%Q", zUser); | |
| 554 | - }else{ | |
| 555 | - blob_append_sql(&sql, ")"); | |
| 556 | - } | |
| 557 | - if( sep==',' ){ | |
| 558 | - db_multi_exec("%s", blob_sql_text(&sql)); | |
| 559 | - } | |
| 560 | - blob_reset(&sql); | |
| 561 | -} | |
| 562 | - | |
| 563 | - | |
| 564 | -/* | |
| 565 | -** Add the select/option box to the timeline submenu that shows | |
| 566 | -** which messages to include in the index. | |
| 567 | -*/ | |
| 568 | -static void webmail_d_submenu(void){ | |
| 569 | - static const char *const az[] = { | |
| 570 | - "0", "InBox", | |
| 571 | - "1", "Unread", | |
| 572 | - "2", "Trash", | |
| 573 | - "3", "Sent", | |
| 574 | - "4", "Everything", | |
| 575 | - }; | |
| 576 | - style_submenu_multichoice("d", sizeof(az)/(2*sizeof(az[0])), az, 0); | |
| 577 | -} | |
| 578 | - | |
| 579 | -/* | |
| 580 | -** WEBPAGE: webmail | |
| 581 | -** | |
| 582 | -** This page can be used to read content from the EMAILBOX table | |
| 583 | -** that contains email received by the "fossil smtpd" command. | |
| 584 | -** | |
| 585 | -** Query parameters: | |
| 586 | -** | |
| 587 | -** id=N Show a single email entry emailbox.ebid==N | |
| 588 | -** f=N Display format. 0: decoded 1: raw | |
| 589 | -** user=USER Show mailbox for USER (admin only). | |
| 590 | -** user=* Show mailbox for all users (admin only). | |
| 591 | -** d=N 0: inbox+unread 1: unread-only 2: trash 3: all | |
| 592 | -** eN Select email entry emailbox.ebid==N | |
| 593 | -** trash Move selected entries to trash (estate=2) | |
| 594 | -** read Mark selected entries as read (estate=1) | |
| 595 | -** unread Mark selected entries as unread (estate=0) | |
| 596 | -** | |
| 597 | -*/ | |
| 598 | -void webmail_page(void){ | |
| 599 | - int emailid; | |
| 600 | - Stmt q; | |
| 601 | - Blob sql; | |
| 602 | - int showAll = 0; | |
| 603 | - const char *zUser = 0; | |
| 604 | - int d = 0; /* Display mode. 0..3. d= query parameter */ | |
| 605 | - int pg = 0; /* Page number */ | |
| 606 | - int N = 50; /* Results per page */ | |
| 607 | - int got; /* Number of results on this page */ | |
| 608 | - char zPPg[30]; /* Previous page */ | |
| 609 | - char zNPg[30]; /* Next page */ | |
| 610 | - HQuery url; | |
| 611 | - login_check_credentials(); | |
| 612 | - if( !login_is_individual() ){ | |
| 613 | - login_needed(0); | |
| 614 | - return; | |
| 615 | - } | |
| 616 | - style_set_current_feature("webmail"); | |
| 617 | - if( !db_table_exists("repository","emailbox") ){ | |
| 618 | - style_header("Webmail Not Available"); | |
| 619 | - @ <p>This repository is not configured to provide webmail</p> | |
| 620 | - style_finish_page(); | |
| 621 | - return; | |
| 622 | - } | |
| 623 | - add_content_sql_commands(g.db); | |
| 624 | - emailid = atoi(PD("id","0")); | |
| 625 | - url_initialize(&url, "webmail"); | |
| 626 | - if( g.perm.Admin ){ | |
| 627 | - zUser = PD("user",g.zLogin); | |
| 628 | - if( zUser ){ | |
| 629 | - url_add_parameter(&url, "user", zUser); | |
| 630 | - if( fossil_strcmp(zUser,"*")==0 ){ | |
| 631 | - showAll = 1; | |
| 632 | - zUser = 0; | |
| 633 | - } | |
| 634 | - } | |
| 635 | - }else{ | |
| 636 | - zUser = g.zLogin; | |
| 637 | - } | |
| 638 | - if( P("d") ) url_add_parameter(&url, "d", P("d")); | |
| 639 | - if( emailid>0 ){ | |
| 640 | - webmail_show_one_message(&url, emailid, zUser); | |
| 641 | - return; | |
| 642 | - } | |
| 643 | - style_header("Webmail"); | |
| 644 | - webmail_d_submenu(); | |
| 645 | - db_begin_transaction(); | |
| 646 | - if( P("trash")!=0 ) webmail_change_state(2,zUser); | |
| 647 | - if( P("unread")!=0 ) webmail_change_state(0,zUser); | |
| 648 | - if( P("read")!=0 ) webmail_change_state(1,zUser); | |
| 649 | - if( P("purge")!=0 ) webmail_change_state(99,zUser); | |
| 650 | - blob_init(&sql, 0, 0); | |
| 651 | - blob_append_sql(&sql, | |
| 652 | - "CREATE TEMP TABLE tmbox AS " | |
| 653 | - "SELECT ebid," /* 0 */ | |
| 654 | - " efrom," /* 1 */ | |
| 655 | - " datetime(edate,'unixepoch')," /* 2 */ | |
| 656 | - " estate," /* 3 */ | |
| 657 | - " esubject," /* 4 */ | |
| 658 | - " euser" /* 5 */ | |
| 659 | - " FROM emailbox" | |
| 660 | - ); | |
| 661 | - d = atoi(PD("d","0")); | |
| 662 | - switch( d ){ | |
| 663 | - case 0: { /* Show unread and read */ | |
| 664 | - blob_append_sql(&sql, " WHERE estate<=1"); | |
| 665 | - break; | |
| 666 | - } | |
| 667 | - case 1: { /* Unread messages only */ | |
| 668 | - blob_append_sql(&sql, " WHERE estate=0"); | |
| 669 | - break; | |
| 670 | - } | |
| 671 | - case 2: { /* Trashcan only */ | |
| 672 | - blob_append_sql(&sql, " WHERE estate=2"); | |
| 673 | - break; | |
| 674 | - } | |
| 675 | - case 3: { /* Outgoing email only */ | |
| 676 | - blob_append_sql(&sql, " WHERE estate=3"); | |
| 677 | - break; | |
| 678 | - } | |
| 679 | - case 4: { /* Everything */ | |
| 680 | - blob_append_sql(&sql, " WHERE 1"); | |
| 681 | - break; | |
| 682 | - } | |
| 683 | - } | |
| 684 | - if( showAll ){ | |
| 685 | - style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); | |
| 686 | - }else if( zUser!=0 ){ | |
| 687 | - style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); | |
| 688 | - if( fossil_strcmp(zUser, g.zLogin)!=0 ){ | |
| 689 | - style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); | |
| 690 | - } | |
| 691 | - if( zUser ){ | |
| 692 | - blob_append_sql(&sql, " AND euser=%Q", zUser); | |
| 693 | - }else{ | |
| 694 | - blob_append_sql(&sql, " AND euser=%Q", g.zLogin); | |
| 695 | - } | |
| 696 | - }else{ | |
| 697 | - if( g.perm.Admin ){ | |
| 698 | - style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); | |
| 699 | - } | |
| 700 | - blob_append_sql(&sql, " AND euser=%Q", g.zLogin); | |
| 701 | - } | |
| 702 | - pg = atoi(PD("pg","0")); | |
| 703 | - blob_append_sql(&sql, " ORDER BY edate DESC limit %d offset %d", N+1, pg*N); | |
| 704 | - db_multi_exec("%s", blob_sql_text(&sql)); | |
| 705 | - got = db_int(0, "SELECT count(*) FROM tmbox"); | |
| 706 | - db_prepare(&q, "SELECT * FROM tmbox LIMIT %d", N); | |
| 707 | - blob_reset(&sql); | |
| 708 | - @ <form action="%R/webmail" method="POST"> | |
| 709 | - @ <input type="hidden" name="d" value="%d(d)"> | |
| 710 | - @ <input type="hidden" name="user" value="%h(zUser?zUser:"*")"> | |
| 711 | - @ <table border="0" width="100%%"> | |
| 712 | - @ <tr><td align="left"> | |
| 713 | - if( d==2 ){ | |
| 714 | - @ <input type="submit" name="read" value="Undelete"> | |
| 715 | - @ <input type="submit" name="purge" value="Delete Permanently"> | |
| 716 | - }else{ | |
| 717 | - @ <input type="submit" name="trash" value="Delete"> | |
| 718 | - if( d!=1 ){ | |
| 719 | - @ <input type="submit" name="unread" value="Mark as unread"> | |
| 720 | - } | |
| 721 | - @ <input type="submit" name="read" value="Mark as read"> | |
| 722 | - } | |
| 723 | - @ <button onclick="webmailSelectAll(); return false;">Select All</button> | |
| 724 | - @ <a href="%h(url_render(&url,0,0,0,0))">refresh</a> | |
| 725 | - @ </td><td align="right"> | |
| 726 | - if( pg>0 ){ | |
| 727 | - sqlite3_snprintf(sizeof(zPPg), zPPg, "%d", pg-1); | |
| 728 | - @ <a href="%s(url_render(&url,"pg",zPPg,0,0))">< Newer</a> | |
| 729 | - } | |
| 730 | - if( got>50 ){ | |
| 731 | - sqlite3_snprintf(sizeof(zNPg),zNPg,"%d",pg+1); | |
| 732 | - @ <a href="%s(url_render(&url,"pg",zNPg,0,0))">Older ></a></td> | |
| 733 | - } | |
| 734 | - @ </table> | |
| 735 | - @ <table> | |
| 736 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 737 | - const char *zId = db_column_text(&q,0); | |
| 738 | - const char *zFrom = db_column_text(&q, 1); | |
| 739 | - const char *zDate = db_column_text(&q, 2); | |
| 740 | - const char *zSubject = db_column_text(&q, 4); | |
| 741 | - if( zSubject==0 || zSubject[0]==0 ) zSubject = "(no subject)"; | |
| 742 | - @ <tr> | |
| 743 | - @ <td><input type="checkbox" class="webmailckbox" name="e%s(zId)"></td> | |
| 744 | - @ <td>%h(zFrom)</td> | |
| 745 | - @ <td><a href="%h(url_render(&url,"id",zId,0,0))">%h(zSubject)</a> \ | |
| 746 | - @ %s(zDate)</td> | |
| 747 | - if( showAll ){ | |
| 748 | - const char *zTo = db_column_text(&q,5); | |
| 749 | - @ <td><a href="%h(url_render(&url,"user",zTo,0,0))">%h(zTo)</a></td> | |
| 750 | - } | |
| 751 | - @ </tr> | |
| 752 | - } | |
| 753 | - db_finalize(&q); | |
| 754 | - @ </table> | |
| 755 | - @ </form> | |
| 756 | - @ <script> | |
| 757 | - @ function webmailSelectAll(){ | |
| 758 | - @ var x = document.getElementsByClassName("webmailckbox"); | |
| 759 | - @ for(i=0; i<x.length; i++){ | |
| 760 | - @ x[i].checked = true; | |
| 761 | - @ } | |
| 762 | - @ } | |
| 763 | - @ </script> | |
| 764 | - style_finish_page(); | |
| 765 | - db_end_transaction(0); | |
| 766 | -} | |
| 767 | - | |
| 768 | -/* | |
| 769 | -** WEBPAGE: emailblob | |
| 770 | -** | |
| 771 | -** This page, accessible only to administrators, allows easy viewing of | |
| 772 | -** the emailblob table - the table that contains the text of email messages | |
| 773 | -** both inbound and outbound, and transcripts of SMTP sessions. | |
| 774 | -** | |
| 775 | -** id=N Show the text of emailblob with emailid==N | |
| 776 | -** | |
| 777 | -*/ | |
| 778 | -void webmail_emailblob_page(void){ | |
| 779 | - int id = atoi(PD("id","0")); | |
| 780 | - Stmt q; | |
| 781 | - login_check_credentials(); | |
| 782 | - if( !g.perm.Setup ){ | |
| 783 | - login_needed(0); | |
| 784 | - return; | |
| 785 | - } | |
| 786 | - add_content_sql_commands(g.db); | |
| 787 | - style_set_current_feature("webmail"); | |
| 788 | - style_header("emailblob table"); | |
| 789 | - if( id>0 ){ | |
| 790 | - style_submenu_element("Index", "%R/emailblob"); | |
| 791 | - @ <ul> | |
| 792 | - db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id); | |
| 793 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 794 | - int id = db_column_int(&q, 0); | |
| 795 | - @ <li> <a href="%R/emailblob?id=%d(id)">emailblob entry %d(id)</a> | |
| 796 | - } | |
| 797 | - db_finalize(&q); | |
| 798 | - db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id); | |
| 799 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 800 | - const char *zUser = db_column_text(&q, 0); | |
| 801 | - int e = db_column_int(&q, 1); | |
| 802 | - @ <li> emailbox for %h(zUser) state %d(e) | |
| 803 | - } | |
| 804 | - db_finalize(&q); | |
| 805 | - db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE emsgid=%d", id); | |
| 806 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 807 | - const char *zFrom = db_column_text(&q, 0); | |
| 808 | - const char *zTo = db_column_text(&q, 1); | |
| 809 | - @ <li> emailoutq message body from %h(zFrom) to %h(zTo) | |
| 810 | - } | |
| 811 | - db_finalize(&q); | |
| 812 | - db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE ets=%d", id); | |
| 813 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 814 | - const char *zFrom = db_column_text(&q, 0); | |
| 815 | - const char *zTo = db_column_text(&q, 1); | |
| 816 | - @ <li> emailoutq transcript from %h(zFrom) to %h(zTo) | |
| 817 | - } | |
| 818 | - db_finalize(&q); | |
| 819 | - @ </ul> | |
| 820 | - @ <hr> | |
| 821 | - db_prepare(&q, "SELECT decompress(etxt) FROM emailblob WHERE emailid=%d", | |
| 822 | - id); | |
| 823 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 824 | - const char *zContent = db_column_text(&q, 0); | |
| 825 | - @ <pre>%h(zContent)</pre> | |
| 826 | - } | |
| 827 | - db_finalize(&q); | |
| 828 | - }else{ | |
| 829 | - style_submenu_element("emailoutq table","%R/emailoutq"); | |
| 830 | - db_prepare(&q, | |
| 831 | - "SELECT emailid, enref, ets, datetime(etime,'unixepoch'), esz," | |
| 832 | - " length(etxt)" | |
| 833 | - " FROM emailblob ORDER BY etime DESC, emailid DESC"); | |
| 834 | - @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \ | |
| 835 | - @ data-column-types='nnntkk'> | |
| 836 | - @ <thead><tr><th> emailid <th> enref <th> ets <th> etime \ | |
| 837 | - @ <th> uncompressed <th> compressed </tr></thead><tbody> | |
| 838 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 839 | - int id = db_column_int(&q, 0); | |
| 840 | - int nref = db_column_int(&q, 1); | |
| 841 | - int ets = db_column_int(&q, 2); | |
| 842 | - const char *zDate = db_column_text(&q, 3); | |
| 843 | - int sz = db_column_int(&q,4); | |
| 844 | - int csz = db_column_int(&q,5); | |
| 845 | - @ <tr> | |
| 846 | - @ <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a> | |
| 847 | - @ <td align="right">%d(nref)</td> | |
| 848 | - if( ets>0 ){ | |
| 849 | - @ <td align="right">%d(ets)</td> | |
| 850 | - }else{ | |
| 851 | - @ <td> </td> | |
| 852 | - } | |
| 853 | - @ <td>%h(zDate)</td> | |
| 854 | - @ <td align="right" data-sortkey='%08x(sz)'>%,d(sz)</td> | |
| 855 | - @ <td align="right" data-sortkey='%08x(csz)'>%,d(csz)</td> | |
| 856 | - @ </tr> | |
| 857 | - } | |
| 858 | - @ </tbody></table> | |
| 859 | - db_finalize(&q); | |
| 860 | - style_table_sorter(); | |
| 861 | - } | |
| 862 | - style_finish_page(); | |
| 863 | -} | |
| 864 | - | |
| 865 | -/* | |
| 866 | -** WEBPAGE: emailoutq | |
| 867 | -** | |
| 868 | -** This page, accessible only to administrators, allows easy viewing of | |
| 869 | -** the emailoutq table - the table that contains the email messages | |
| 870 | -** that are queued for transmission via SMTP. | |
| 871 | -*/ | |
| 872 | -void webmail_emailoutq_page(void){ | |
| 873 | - Stmt q; | |
| 874 | - login_check_credentials(); | |
| 875 | - if( !g.perm.Setup ){ | |
| 876 | - login_needed(0); | |
| 877 | - return; | |
| 878 | - } | |
| 879 | - add_content_sql_commands(g.db); | |
| 880 | - style_set_current_feature("webmail"); | |
| 881 | - style_header("emailoutq table"); | |
| 882 | - style_submenu_element("emailblob table","%R/emailblob"); | |
| 883 | - db_prepare(&q, | |
| 884 | - "SELECT edomain, efrom, eto, emsgid, " | |
| 885 | - " datetime(ectime,'unixepoch')," | |
| 886 | - " datetime(nullif(emtime,0),'unixepoch')," | |
| 887 | - " ensend, ets" | |
| 888 | - " FROM emailoutq" | |
| 889 | - ); | |
| 890 | - @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \ | |
| 891 | - @ data-column-types='tttnttnn'> | |
| 892 | - @ <thead><tr><th> edomain <th> efrom <th> eto <th> emsgid \ | |
| 893 | - @ <th> ectime <th> emtime <th> ensend <th> ets </tr></thead><tbody> | |
| 894 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 895 | - const char *zDomain = db_column_text(&q, 0); | |
| 896 | - const char *zFrom = db_column_text(&q, 1); | |
| 897 | - const char *zTo = db_column_text(&q, 2); | |
| 898 | - int emsgid = db_column_int(&q, 3); | |
| 899 | - const char *zCTime = db_column_text(&q, 4); | |
| 900 | - const char *zMTime = db_column_text(&q, 5); | |
| 901 | - int ensend = db_column_int(&q, 6); | |
| 902 | - int ets = db_column_int(&q, 7); | |
| 903 | - @ <tr> | |
| 904 | - @ <td>%h(zDomain) | |
| 905 | - @ <td>%h(zFrom) | |
| 906 | - @ <td>%h(zTo) | |
| 907 | - @ <td align="right"><a href="%R/emailblob?id=%d(emsgid)">%d(emsgid)</a> | |
| 908 | - @ <td>%h(zCTime) | |
| 909 | - @ <td>%h(zMTime) | |
| 910 | - @ <td align="right">%d(ensend) | |
| 911 | - if( ets>0 ){ | |
| 912 | - @ <td align="right"><a href="%R/emailblob?id=%d(ets)">%d(ets)</a></td> | |
| 913 | - }else{ | |
| 914 | - @ <td> </td> | |
| 915 | - } | |
| 916 | - } | |
| 917 | - @ </tbody></table> | |
| 918 | - db_finalize(&q); | |
| 919 | - style_table_sorter(); | |
| 920 | - style_finish_page(); | |
| 921 | -} |
| --- a/src/webmail.c | |
| +++ b/src/webmail.c | |
| @@ -1,921 +0,0 @@ | |
| 1 | /* |
| 2 | ** Copyright (c) 2018 D. Richard Hipp |
| 3 | ** |
| 4 | ** This program is free software; you can redistribute it and/or |
| 5 | ** modify it under the terms of the Simplified BSD License (also |
| 6 | ** known as the "2-Clause License" or "FreeBSD License".) |
| 7 | ** |
| 8 | ** This program is distributed in the hope that it will be useful, |
| 9 | ** but without any warranty; without even the implied warranty of |
| 10 | ** merchantability or fitness for a particular purpose. |
| 11 | ** |
| 12 | ** Author contact information: |
| 13 | ** [email protected] |
| 14 | ** http://www.hwaci.com/drh/ |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** Implementation of web pages for managing the email storage tables |
| 19 | ** (if they exist): |
| 20 | ** |
| 21 | ** emailbox |
| 22 | ** emailblob |
| 23 | ** emailroute |
| 24 | */ |
| 25 | #include "config.h" |
| 26 | #include "webmail.h" |
| 27 | #include <assert.h> |
| 28 | |
| 29 | |
| 30 | #if INTERFACE |
| 31 | |
| 32 | /* Recognized content encodings */ |
| 33 | #define EMAILENC_NONE 0 /* No encoding */ |
| 34 | #define EMAILENC_B64 1 /* Base64 encoded */ |
| 35 | #define EMAILENC_QUOTED 2 /* Quoted printable */ |
| 36 | |
| 37 | /* An instance of the following object records the location of important |
| 38 | ** attributes on a single element in a multipart email message body. |
| 39 | */ |
| 40 | struct EmailBody { |
| 41 | char zMimetype[32]; /* Mimetype */ |
| 42 | u8 encoding; /* Type of encoding */ |
| 43 | char *zFilename; /* From content-disposition: */ |
| 44 | char *zContent; /* Content. \0 terminator inserted */ |
| 45 | }; |
| 46 | |
| 47 | /* |
| 48 | ** An instance of the following object describes the struture of |
| 49 | ** an rfc-2822 email message. |
| 50 | */ |
| 51 | struct EmailToc { |
| 52 | int nHdr; /* Number of header lines */ |
| 53 | int nHdrAlloc; /* Number of header lines allocated */ |
| 54 | char **azHdr; /* Pointer to header line. \0 terminator inserted */ |
| 55 | int nBody; /* Number of body segments */ |
| 56 | int nBodyAlloc; /* Number of body segments allocated */ |
| 57 | EmailBody *aBody; /* Location of body information */ |
| 58 | }; |
| 59 | #endif |
| 60 | |
| 61 | /* |
| 62 | ** Free An EmailToc object |
| 63 | */ |
| 64 | void emailtoc_free(EmailToc *p){ |
| 65 | int i; |
| 66 | fossil_free(p->azHdr); |
| 67 | for(i=0; i<p->nBody; i++){ |
| 68 | fossil_free(p->aBody[i].zFilename); |
| 69 | } |
| 70 | fossil_free(p->aBody); |
| 71 | fossil_free(p); |
| 72 | } |
| 73 | |
| 74 | /* |
| 75 | ** Allocate a new EmailToc object |
| 76 | */ |
| 77 | EmailToc *emailtoc_alloc(void){ |
| 78 | EmailToc *p = fossil_malloc( sizeof(*p) ); |
| 79 | memset(p, 0, sizeof(*p)); |
| 80 | return p; |
| 81 | } |
| 82 | |
| 83 | /* |
| 84 | ** Add a new body element to an EmailToc. |
| 85 | */ |
| 86 | EmailBody *emailtoc_new_body(EmailToc *p){ |
| 87 | EmailBody *pNew; |
| 88 | p->nBody++; |
| 89 | if( p->nBody>p->nBodyAlloc ){ |
| 90 | p->nBodyAlloc = (p->nBodyAlloc+1)*2; |
| 91 | p->aBody = fossil_realloc(p->aBody, sizeof(p->aBody[0])*p->nBodyAlloc); |
| 92 | } |
| 93 | pNew = &p->aBody[p->nBody-1]; |
| 94 | memset(pNew, 0, sizeof(*pNew)); |
| 95 | return pNew; |
| 96 | } |
| 97 | |
| 98 | /* |
| 99 | ** Add a new header line to the EmailToc. |
| 100 | */ |
| 101 | void emailtoc_new_header_line(EmailToc *p, char *z){ |
| 102 | p->nHdr++; |
| 103 | if( p->nHdr>p->nHdrAlloc ){ |
| 104 | p->nHdrAlloc = (p->nHdrAlloc+1)*2; |
| 105 | p->azHdr = fossil_realloc(p->azHdr, sizeof(p->azHdr[0])*p->nHdrAlloc); |
| 106 | } |
| 107 | p->azHdr[p->nHdr-1] = z; |
| 108 | } |
| 109 | |
| 110 | /* |
| 111 | ** Return the length of a line in an email header. Continuation lines |
| 112 | ** are included. Hence, this routine returns the number of bytes up to |
| 113 | ** and including the first \n character that is followed by something |
| 114 | ** other than whitespace. |
| 115 | */ |
| 116 | static int email_line_length(const char *z){ |
| 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){ |
| 150 | while( fossil_isspace(*z) ){ |
| 151 | z++; |
| 152 | } |
| 153 | return z; |
| 154 | } |
| 155 | |
| 156 | /* |
| 157 | ** The n-bytes of content in z is a single multipart mime segment |
| 158 | ** with its own header and body. Decode this one segment and add it to p; |
| 159 | ** |
| 160 | ** Rows of the header of the segment are added to p if bAddHeader is |
| 161 | ** true. |
| 162 | */ |
| 163 | LOCAL void emailtoc_add_multipart_segment( |
| 164 | EmailToc *p, /* Append the segments here */ |
| 165 | char *z, /* The body component */ |
| 166 | int bAddHeader /* True to add header lines to p */ |
| 167 | ){ |
| 168 | int i, j; |
| 169 | int n; |
| 170 | int multipartBody = 0; |
| 171 | EmailBody *pBody = emailtoc_new_body(p); |
| 172 | i = 0; |
| 173 | while( z[i] ){ |
| 174 | n = email_line_length(&z[i]); |
| 175 | if( (n==2 && z[i]=='\r' && z[i+1]=='\n') || z[i]=='\n' || n==0 ){ |
| 176 | /* This is the blank line at the end of the header */ |
| 177 | i += n; |
| 178 | break; |
| 179 | } |
| 180 | for(j=i+n; j>i && fossil_isspace(z[j-1]); j--){} |
| 181 | z[j] = 0; |
| 182 | if( sqlite3_strnicmp(z+i, "Content-Type:", 13)==0 ){ |
| 183 | const char *z2 = firstToken(z+i+13); |
| 184 | if( z2 && strncmp(z2, "multipart/", 10)==0 ){ |
| 185 | multipartBody = 1; |
| 186 | }else{ |
| 187 | int j; |
| 188 | for(j=0; z2[j]=='/' || fossil_isalnum(z2[j]); j++){} |
| 189 | if( j>=sizeof(pBody->zMimetype) ) j = sizeof(pBody->zMimetype); |
| 190 | memcpy(pBody->zMimetype, z2, j); |
| 191 | pBody->zMimetype[j] = 0; |
| 192 | } |
| 193 | } |
| 194 | /* 123456789 123456789 123456 */ |
| 195 | if( sqlite3_strnicmp(z+i, "Content-Transfer-Encoding:", 26)==0 ){ |
| 196 | const char *z2 = firstToken(z+(i+26)); |
| 197 | if( z2 && sqlite3_strnicmp(z2, "base64", 6)==0 ){ |
| 198 | pBody->encoding = EMAILENC_B64; |
| 199 | /* 123456789 123456 */ |
| 200 | }else if( sqlite3_strnicmp(z2, "quoted-printable", 16)==0 ){ |
| 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); |
| 218 | }else{ |
| 219 | pBody->zContent = z+i; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /* |
| 224 | ** The n-bytes of content in z are a multipart/ body component for |
| 225 | ** an email message. Decode this into its individual segments. |
| 226 | ** |
| 227 | ** The component should start and end with a boundary line. There |
| 228 | ** may be additional boundary lines in the middle. |
| 229 | */ |
| 230 | LOCAL void emailtoc_add_multipart( |
| 231 | EmailToc *p, /* Append the segments here */ |
| 232 | char *z /* The body component. zero-terminated */ |
| 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 ){ |
| 257 | z[i+1] = 0; |
| 258 | emailtoc_add_multipart_segment(p, z+iStart, 0); |
| 259 | iStart = i+nB; |
| 260 | if( z[iStart]=='-' && z[iStart+1]=='-' ) return; |
| 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 | ** |
| 271 | ** This routine will cause pEmail to become zero-terminated if it is |
| 272 | ** not already. It will also insert zero characters into parts of |
| 273 | ** the message, to delimit the various components. |
| 274 | */ |
| 275 | EmailToc *emailtoc_from_email(Blob *pEmail){ |
| 276 | char *z; |
| 277 | EmailToc *p = emailtoc_alloc(); |
| 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 |
| 307 | ** |
| 308 | ** Read an rfc-2822 formatted email out of FILE, then write a decoding |
| 309 | ** to stdout. Use for testing and validating the email decoder. |
| 310 | */ |
| 311 | void test_email_decode_cmd(void){ |
| 312 | Blob email; |
| 313 | EmailToc *p; |
| 314 | int i; |
| 315 | verify_all_options(); |
| 316 | if( g.argc!=3 ) usage("FILE"); |
| 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); |
| 338 | if( n && p->aBody[i].zContent[n-1]!='\n' ) fossil_print("\n"); |
| 339 | break; |
| 340 | } |
| 341 | case EMAILENC_QUOTED: { |
| 342 | int n = 0; |
| 343 | decodeQuotedPrintable(p->aBody[i].zContent, &n); |
| 344 | fossil_print("%s", p->aBody[i].zContent); |
| 345 | if( n && p->aBody[i].zContent[n-1]!='\n' ) fossil_print("\n"); |
| 346 | break; |
| 347 | } |
| 348 | default: { |
| 349 | fossil_print("%s\n", p->aBody[i].zContent); |
| 350 | break; |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | emailtoc_free(p); |
| 355 | blob_reset(&email); |
| 356 | } |
| 357 | |
| 358 | /* |
| 359 | ** Add the select/option box to the timeline submenu that shows |
| 360 | ** the various email message formats. |
| 361 | */ |
| 362 | static void webmail_f_submenu(void){ |
| 363 | static const char *const az[] = { |
| 364 | "0", "Normal", |
| 365 | "1", "Decoded", |
| 366 | "2", "Raw", |
| 367 | }; |
| 368 | style_submenu_multichoice("f", sizeof(az)/(2*sizeof(az[0])), az, 0); |
| 369 | } |
| 370 | |
| 371 | /* |
| 372 | ** If the first N characters of z[] are the name of a header field |
| 373 | ** that should be shown in "Normal" mode, then return 1. |
| 374 | */ |
| 375 | static int webmail_normal_header(const char *z, int N){ |
| 376 | static const char *const az[] = { |
| 377 | "To", "Cc", "Bcc", "Date", "From", "Subject", |
| 378 | }; |
| 379 | int i; |
| 380 | for(i=0; i<sizeof(az)/sizeof(az[0]); i++){ |
| 381 | if( sqlite3_strnicmp(z, az[i], N)==0 ) return 1; |
| 382 | } |
| 383 | return 0; |
| 384 | } |
| 385 | |
| 386 | /* |
| 387 | ** Paint a page showing a single email message |
| 388 | */ |
| 389 | static void webmail_show_one_message( |
| 390 | HQuery *pUrl, /* Calling context */ |
| 391 | int emailid, /* emailbox.ebid to display */ |
| 392 | const char *zUser /* User who owns it, or NULL if does not matter */ |
| 393 | ){ |
| 394 | Blob sql; |
| 395 | Stmt q; |
| 396 | int eState = -1; |
| 397 | int eTranscript = 0; |
| 398 | char zENum[30]; |
| 399 | style_submenu_element("Index", "%s", url_render(pUrl,"id",0,0,0)); |
| 400 | webmail_f_submenu(); |
| 401 | blob_init(&sql, 0, 0); |
| 402 | db_begin_transaction(); |
| 403 | blob_append_sql(&sql, |
| 404 | "SELECT decompress(etxt), estate, emailblob.ets" |
| 405 | " FROM emailblob, emailbox" |
| 406 | " WHERE emailid=emsgid AND ebid=%d", |
| 407 | emailid |
| 408 | ); |
| 409 | if( zUser ) blob_append_sql(&sql, " AND euser=%Q", zUser); |
| 410 | db_prepare_blob(&q, &sql); |
| 411 | blob_reset(&sql); |
| 412 | style_set_current_feature("webmail"); |
| 413 | style_header("Message %d",emailid); |
| 414 | if( db_step(&q)==SQLITE_ROW ){ |
| 415 | Blob msg = db_column_text_as_blob(&q, 0); |
| 416 | int eFormat = atoi(PD("f","0")); |
| 417 | eState = db_column_int(&q, 1); |
| 418 | eTranscript = db_column_int(&q, 2); |
| 419 | if( eFormat==2 ){ |
| 420 | @ <pre>%h(db_column_text(&q, 0))</pre> |
| 421 | }else{ |
| 422 | EmailToc *p = emailtoc_from_email(&msg); |
| 423 | int i, j; |
| 424 | @ <p> |
| 425 | for(i=0; i<p->nHdr; i++){ |
| 426 | char *z = p->azHdr[i]; |
| 427 | email_hdr_unfold(z); |
| 428 | for(j=0; z[j] && z[j]!=':'; j++){} |
| 429 | if( eFormat==0 && !webmail_normal_header(z, j) ) continue; |
| 430 | if( z[j]!=':' ){ |
| 431 | @ %h(z)<br> |
| 432 | }else{ |
| 433 | z[j] = 0; |
| 434 | @ <b>%h(z):</b> %h(z+j+1)<br> |
| 435 | } |
| 436 | } |
| 437 | for(i=0; i<p->nBody; i++){ |
| 438 | @ <hr><b>Messsage Body #%d(i): %h(p->aBody[i].zMimetype) \ |
| 439 | if( p->aBody[i].zFilename ){ |
| 440 | @ "%h(p->aBody[i].zFilename)" |
| 441 | } |
| 442 | @ </b> |
| 443 | if( eFormat==0 ){ |
| 444 | if( strncmp(p->aBody[i].zMimetype, "text/plain", 10)!=0 ) continue; |
| 445 | if( p->aBody[i].zFilename ) continue; |
| 446 | }else{ |
| 447 | if( strncmp(p->aBody[i].zMimetype, "text/", 5)!=0 ) continue; |
| 448 | } |
| 449 | switch( p->aBody[i].encoding ){ |
| 450 | case EMAILENC_B64: { |
| 451 | int n = 0; |
| 452 | decodeBase64(p->aBody[i].zContent, &n, p->aBody[i].zContent); |
| 453 | break; |
| 454 | } |
| 455 | case EMAILENC_QUOTED: { |
| 456 | int n = 0; |
| 457 | decodeQuotedPrintable(p->aBody[i].zContent, &n); |
| 458 | break; |
| 459 | } |
| 460 | } |
| 461 | @ <pre>%h(p->aBody[i].zContent)</pre> |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | db_finalize(&q); |
| 466 | |
| 467 | /* Optionally show the SMTP transcript */ |
| 468 | if( eTranscript>0 |
| 469 | && db_exists("SELECT 1 FROM emailblob WHERE emailid=%d", eTranscript) |
| 470 | ){ |
| 471 | if( P("ts")==0 ){ |
| 472 | sqlite3_snprintf(sizeof(zENum), zENum, "%d", emailid); |
| 473 | style_submenu_element("SMTP Transcript","%s", |
| 474 | url_render(pUrl, "ts", "1", "id", zENum)); |
| 475 | }else{ |
| 476 | db_prepare(&q, |
| 477 | "SELECT decompress(etxt) FROM emailblob WHERE emailid=%d", eTranscript |
| 478 | ); |
| 479 | if( db_step(&q)==SQLITE_ROW ){ |
| 480 | const char *zTranscript = db_column_text(&q, 0); |
| 481 | @ <hr> |
| 482 | @ <pre>%h(zTranscript)</pre> |
| 483 | } |
| 484 | db_finalize(&q); |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | if( eState==0 ){ |
| 489 | /* If is message is currently Unread, change it to Read */ |
| 490 | blob_append_sql(&sql, |
| 491 | "UPDATE emailbox SET estate=1 " |
| 492 | " WHERE estate=0 AND ebid=%d", |
| 493 | emailid |
| 494 | ); |
| 495 | if( zUser ) blob_append_sql(&sql, " AND euser=%Q", zUser); |
| 496 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 497 | blob_reset(&sql); |
| 498 | eState = 1; |
| 499 | } |
| 500 | |
| 501 | url_add_parameter(pUrl, "id", 0); |
| 502 | sqlite3_snprintf(sizeof(zENum), zENum, "e%d", emailid); |
| 503 | if( eState==2 ){ |
| 504 | style_submenu_element("Undelete","%s", |
| 505 | url_render(pUrl,"read","1",zENum,"1")); |
| 506 | } |
| 507 | if( eState==1 ){ |
| 508 | style_submenu_element("Delete", "%s", |
| 509 | url_render(pUrl,"trash","1",zENum,"1")); |
| 510 | style_submenu_element("Mark As Unread", "%s", |
| 511 | url_render(pUrl,"unread","1",zENum,"1")); |
| 512 | } |
| 513 | if( eState==3 ){ |
| 514 | style_submenu_element("Delete", "%s", |
| 515 | url_render(pUrl,"trash","1",zENum,"1")); |
| 516 | } |
| 517 | |
| 518 | db_end_transaction(0); |
| 519 | style_finish_page(); |
| 520 | return; |
| 521 | } |
| 522 | |
| 523 | /* |
| 524 | ** Scan the query parameters looking for parameters with name of the |
| 525 | ** form "eN" where N is an integer. For all such integers, change |
| 526 | ** the state of every emailbox entry with ebid==N to eStateNew provided |
| 527 | ** that either zUser is NULL or matches. |
| 528 | ** |
| 529 | ** Or if eNewState==99, then delete the entries. |
| 530 | */ |
| 531 | static void webmail_change_state(int eNewState, const char *zUser){ |
| 532 | Blob sql; |
| 533 | int sep = '('; |
| 534 | int i; |
| 535 | const char *zName; |
| 536 | int n; |
| 537 | if( !cgi_csrf_safe(0) ) return; |
| 538 | blob_init(&sql, 0, 0); |
| 539 | if( eNewState==99 ){ |
| 540 | blob_append_sql(&sql, "DELETE FROM emailbox WHERE estate==2 AND ebid IN "); |
| 541 | }else{ |
| 542 | blob_append_sql(&sql, "UPDATE emailbox SET estate=%d WHERE ebid IN ", |
| 543 | eNewState); |
| 544 | } |
| 545 | for(i=0; (zName = cgi_parameter_name(i))!=0; i++){ |
| 546 | if( zName[0]!='e' ) continue; |
| 547 | if( !fossil_isdigit(zName[1]) ) continue; |
| 548 | n = atoi(zName+1); |
| 549 | blob_append_sql(&sql, "%c%d", sep, n); |
| 550 | sep = ','; |
| 551 | } |
| 552 | if( zUser ){ |
| 553 | blob_append_sql(&sql, ") AND euser=%Q", zUser); |
| 554 | }else{ |
| 555 | blob_append_sql(&sql, ")"); |
| 556 | } |
| 557 | if( sep==',' ){ |
| 558 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 559 | } |
| 560 | blob_reset(&sql); |
| 561 | } |
| 562 | |
| 563 | |
| 564 | /* |
| 565 | ** Add the select/option box to the timeline submenu that shows |
| 566 | ** which messages to include in the index. |
| 567 | */ |
| 568 | static void webmail_d_submenu(void){ |
| 569 | static const char *const az[] = { |
| 570 | "0", "InBox", |
| 571 | "1", "Unread", |
| 572 | "2", "Trash", |
| 573 | "3", "Sent", |
| 574 | "4", "Everything", |
| 575 | }; |
| 576 | style_submenu_multichoice("d", sizeof(az)/(2*sizeof(az[0])), az, 0); |
| 577 | } |
| 578 | |
| 579 | /* |
| 580 | ** WEBPAGE: webmail |
| 581 | ** |
| 582 | ** This page can be used to read content from the EMAILBOX table |
| 583 | ** that contains email received by the "fossil smtpd" command. |
| 584 | ** |
| 585 | ** Query parameters: |
| 586 | ** |
| 587 | ** id=N Show a single email entry emailbox.ebid==N |
| 588 | ** f=N Display format. 0: decoded 1: raw |
| 589 | ** user=USER Show mailbox for USER (admin only). |
| 590 | ** user=* Show mailbox for all users (admin only). |
| 591 | ** d=N 0: inbox+unread 1: unread-only 2: trash 3: all |
| 592 | ** eN Select email entry emailbox.ebid==N |
| 593 | ** trash Move selected entries to trash (estate=2) |
| 594 | ** read Mark selected entries as read (estate=1) |
| 595 | ** unread Mark selected entries as unread (estate=0) |
| 596 | ** |
| 597 | */ |
| 598 | void webmail_page(void){ |
| 599 | int emailid; |
| 600 | Stmt q; |
| 601 | Blob sql; |
| 602 | int showAll = 0; |
| 603 | const char *zUser = 0; |
| 604 | int d = 0; /* Display mode. 0..3. d= query parameter */ |
| 605 | int pg = 0; /* Page number */ |
| 606 | int N = 50; /* Results per page */ |
| 607 | int got; /* Number of results on this page */ |
| 608 | char zPPg[30]; /* Previous page */ |
| 609 | char zNPg[30]; /* Next page */ |
| 610 | HQuery url; |
| 611 | login_check_credentials(); |
| 612 | if( !login_is_individual() ){ |
| 613 | login_needed(0); |
| 614 | return; |
| 615 | } |
| 616 | style_set_current_feature("webmail"); |
| 617 | if( !db_table_exists("repository","emailbox") ){ |
| 618 | style_header("Webmail Not Available"); |
| 619 | @ <p>This repository is not configured to provide webmail</p> |
| 620 | style_finish_page(); |
| 621 | return; |
| 622 | } |
| 623 | add_content_sql_commands(g.db); |
| 624 | emailid = atoi(PD("id","0")); |
| 625 | url_initialize(&url, "webmail"); |
| 626 | if( g.perm.Admin ){ |
| 627 | zUser = PD("user",g.zLogin); |
| 628 | if( zUser ){ |
| 629 | url_add_parameter(&url, "user", zUser); |
| 630 | if( fossil_strcmp(zUser,"*")==0 ){ |
| 631 | showAll = 1; |
| 632 | zUser = 0; |
| 633 | } |
| 634 | } |
| 635 | }else{ |
| 636 | zUser = g.zLogin; |
| 637 | } |
| 638 | if( P("d") ) url_add_parameter(&url, "d", P("d")); |
| 639 | if( emailid>0 ){ |
| 640 | webmail_show_one_message(&url, emailid, zUser); |
| 641 | return; |
| 642 | } |
| 643 | style_header("Webmail"); |
| 644 | webmail_d_submenu(); |
| 645 | db_begin_transaction(); |
| 646 | if( P("trash")!=0 ) webmail_change_state(2,zUser); |
| 647 | if( P("unread")!=0 ) webmail_change_state(0,zUser); |
| 648 | if( P("read")!=0 ) webmail_change_state(1,zUser); |
| 649 | if( P("purge")!=0 ) webmail_change_state(99,zUser); |
| 650 | blob_init(&sql, 0, 0); |
| 651 | blob_append_sql(&sql, |
| 652 | "CREATE TEMP TABLE tmbox AS " |
| 653 | "SELECT ebid," /* 0 */ |
| 654 | " efrom," /* 1 */ |
| 655 | " datetime(edate,'unixepoch')," /* 2 */ |
| 656 | " estate," /* 3 */ |
| 657 | " esubject," /* 4 */ |
| 658 | " euser" /* 5 */ |
| 659 | " FROM emailbox" |
| 660 | ); |
| 661 | d = atoi(PD("d","0")); |
| 662 | switch( d ){ |
| 663 | case 0: { /* Show unread and read */ |
| 664 | blob_append_sql(&sql, " WHERE estate<=1"); |
| 665 | break; |
| 666 | } |
| 667 | case 1: { /* Unread messages only */ |
| 668 | blob_append_sql(&sql, " WHERE estate=0"); |
| 669 | break; |
| 670 | } |
| 671 | case 2: { /* Trashcan only */ |
| 672 | blob_append_sql(&sql, " WHERE estate=2"); |
| 673 | break; |
| 674 | } |
| 675 | case 3: { /* Outgoing email only */ |
| 676 | blob_append_sql(&sql, " WHERE estate=3"); |
| 677 | break; |
| 678 | } |
| 679 | case 4: { /* Everything */ |
| 680 | blob_append_sql(&sql, " WHERE 1"); |
| 681 | break; |
| 682 | } |
| 683 | } |
| 684 | if( showAll ){ |
| 685 | style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); |
| 686 | }else if( zUser!=0 ){ |
| 687 | style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); |
| 688 | if( fossil_strcmp(zUser, g.zLogin)!=0 ){ |
| 689 | style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); |
| 690 | } |
| 691 | if( zUser ){ |
| 692 | blob_append_sql(&sql, " AND euser=%Q", zUser); |
| 693 | }else{ |
| 694 | blob_append_sql(&sql, " AND euser=%Q", g.zLogin); |
| 695 | } |
| 696 | }else{ |
| 697 | if( g.perm.Admin ){ |
| 698 | style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); |
| 699 | } |
| 700 | blob_append_sql(&sql, " AND euser=%Q", g.zLogin); |
| 701 | } |
| 702 | pg = atoi(PD("pg","0")); |
| 703 | blob_append_sql(&sql, " ORDER BY edate DESC limit %d offset %d", N+1, pg*N); |
| 704 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 705 | got = db_int(0, "SELECT count(*) FROM tmbox"); |
| 706 | db_prepare(&q, "SELECT * FROM tmbox LIMIT %d", N); |
| 707 | blob_reset(&sql); |
| 708 | @ <form action="%R/webmail" method="POST"> |
| 709 | @ <input type="hidden" name="d" value="%d(d)"> |
| 710 | @ <input type="hidden" name="user" value="%h(zUser?zUser:"*")"> |
| 711 | @ <table border="0" width="100%%"> |
| 712 | @ <tr><td align="left"> |
| 713 | if( d==2 ){ |
| 714 | @ <input type="submit" name="read" value="Undelete"> |
| 715 | @ <input type="submit" name="purge" value="Delete Permanently"> |
| 716 | }else{ |
| 717 | @ <input type="submit" name="trash" value="Delete"> |
| 718 | if( d!=1 ){ |
| 719 | @ <input type="submit" name="unread" value="Mark as unread"> |
| 720 | } |
| 721 | @ <input type="submit" name="read" value="Mark as read"> |
| 722 | } |
| 723 | @ <button onclick="webmailSelectAll(); return false;">Select All</button> |
| 724 | @ <a href="%h(url_render(&url,0,0,0,0))">refresh</a> |
| 725 | @ </td><td align="right"> |
| 726 | if( pg>0 ){ |
| 727 | sqlite3_snprintf(sizeof(zPPg), zPPg, "%d", pg-1); |
| 728 | @ <a href="%s(url_render(&url,"pg",zPPg,0,0))">< Newer</a> |
| 729 | } |
| 730 | if( got>50 ){ |
| 731 | sqlite3_snprintf(sizeof(zNPg),zNPg,"%d",pg+1); |
| 732 | @ <a href="%s(url_render(&url,"pg",zNPg,0,0))">Older ></a></td> |
| 733 | } |
| 734 | @ </table> |
| 735 | @ <table> |
| 736 | while( db_step(&q)==SQLITE_ROW ){ |
| 737 | const char *zId = db_column_text(&q,0); |
| 738 | const char *zFrom = db_column_text(&q, 1); |
| 739 | const char *zDate = db_column_text(&q, 2); |
| 740 | const char *zSubject = db_column_text(&q, 4); |
| 741 | if( zSubject==0 || zSubject[0]==0 ) zSubject = "(no subject)"; |
| 742 | @ <tr> |
| 743 | @ <td><input type="checkbox" class="webmailckbox" name="e%s(zId)"></td> |
| 744 | @ <td>%h(zFrom)</td> |
| 745 | @ <td><a href="%h(url_render(&url,"id",zId,0,0))">%h(zSubject)</a> \ |
| 746 | @ %s(zDate)</td> |
| 747 | if( showAll ){ |
| 748 | const char *zTo = db_column_text(&q,5); |
| 749 | @ <td><a href="%h(url_render(&url,"user",zTo,0,0))">%h(zTo)</a></td> |
| 750 | } |
| 751 | @ </tr> |
| 752 | } |
| 753 | db_finalize(&q); |
| 754 | @ </table> |
| 755 | @ </form> |
| 756 | @ <script> |
| 757 | @ function webmailSelectAll(){ |
| 758 | @ var x = document.getElementsByClassName("webmailckbox"); |
| 759 | @ for(i=0; i<x.length; i++){ |
| 760 | @ x[i].checked = true; |
| 761 | @ } |
| 762 | @ } |
| 763 | @ </script> |
| 764 | style_finish_page(); |
| 765 | db_end_transaction(0); |
| 766 | } |
| 767 | |
| 768 | /* |
| 769 | ** WEBPAGE: emailblob |
| 770 | ** |
| 771 | ** This page, accessible only to administrators, allows easy viewing of |
| 772 | ** the emailblob table - the table that contains the text of email messages |
| 773 | ** both inbound and outbound, and transcripts of SMTP sessions. |
| 774 | ** |
| 775 | ** id=N Show the text of emailblob with emailid==N |
| 776 | ** |
| 777 | */ |
| 778 | void webmail_emailblob_page(void){ |
| 779 | int id = atoi(PD("id","0")); |
| 780 | Stmt q; |
| 781 | login_check_credentials(); |
| 782 | if( !g.perm.Setup ){ |
| 783 | login_needed(0); |
| 784 | return; |
| 785 | } |
| 786 | add_content_sql_commands(g.db); |
| 787 | style_set_current_feature("webmail"); |
| 788 | style_header("emailblob table"); |
| 789 | if( id>0 ){ |
| 790 | style_submenu_element("Index", "%R/emailblob"); |
| 791 | @ <ul> |
| 792 | db_prepare(&q, "SELECT emailid FROM emailblob WHERE ets=%d", id); |
| 793 | while( db_step(&q)==SQLITE_ROW ){ |
| 794 | int id = db_column_int(&q, 0); |
| 795 | @ <li> <a href="%R/emailblob?id=%d(id)">emailblob entry %d(id)</a> |
| 796 | } |
| 797 | db_finalize(&q); |
| 798 | db_prepare(&q, "SELECT euser, estate FROM emailbox WHERE emsgid=%d", id); |
| 799 | while( db_step(&q)==SQLITE_ROW ){ |
| 800 | const char *zUser = db_column_text(&q, 0); |
| 801 | int e = db_column_int(&q, 1); |
| 802 | @ <li> emailbox for %h(zUser) state %d(e) |
| 803 | } |
| 804 | db_finalize(&q); |
| 805 | db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE emsgid=%d", id); |
| 806 | while( db_step(&q)==SQLITE_ROW ){ |
| 807 | const char *zFrom = db_column_text(&q, 0); |
| 808 | const char *zTo = db_column_text(&q, 1); |
| 809 | @ <li> emailoutq message body from %h(zFrom) to %h(zTo) |
| 810 | } |
| 811 | db_finalize(&q); |
| 812 | db_prepare(&q, "SELECT efrom, eto FROM emailoutq WHERE ets=%d", id); |
| 813 | while( db_step(&q)==SQLITE_ROW ){ |
| 814 | const char *zFrom = db_column_text(&q, 0); |
| 815 | const char *zTo = db_column_text(&q, 1); |
| 816 | @ <li> emailoutq transcript from %h(zFrom) to %h(zTo) |
| 817 | } |
| 818 | db_finalize(&q); |
| 819 | @ </ul> |
| 820 | @ <hr> |
| 821 | db_prepare(&q, "SELECT decompress(etxt) FROM emailblob WHERE emailid=%d", |
| 822 | id); |
| 823 | while( db_step(&q)==SQLITE_ROW ){ |
| 824 | const char *zContent = db_column_text(&q, 0); |
| 825 | @ <pre>%h(zContent)</pre> |
| 826 | } |
| 827 | db_finalize(&q); |
| 828 | }else{ |
| 829 | style_submenu_element("emailoutq table","%R/emailoutq"); |
| 830 | db_prepare(&q, |
| 831 | "SELECT emailid, enref, ets, datetime(etime,'unixepoch'), esz," |
| 832 | " length(etxt)" |
| 833 | " FROM emailblob ORDER BY etime DESC, emailid DESC"); |
| 834 | @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \ |
| 835 | @ data-column-types='nnntkk'> |
| 836 | @ <thead><tr><th> emailid <th> enref <th> ets <th> etime \ |
| 837 | @ <th> uncompressed <th> compressed </tr></thead><tbody> |
| 838 | while( db_step(&q)==SQLITE_ROW ){ |
| 839 | int id = db_column_int(&q, 0); |
| 840 | int nref = db_column_int(&q, 1); |
| 841 | int ets = db_column_int(&q, 2); |
| 842 | const char *zDate = db_column_text(&q, 3); |
| 843 | int sz = db_column_int(&q,4); |
| 844 | int csz = db_column_int(&q,5); |
| 845 | @ <tr> |
| 846 | @ <td align="right"><a href="%R/emailblob?id=%d(id)">%d(id)</a> |
| 847 | @ <td align="right">%d(nref)</td> |
| 848 | if( ets>0 ){ |
| 849 | @ <td align="right">%d(ets)</td> |
| 850 | }else{ |
| 851 | @ <td> </td> |
| 852 | } |
| 853 | @ <td>%h(zDate)</td> |
| 854 | @ <td align="right" data-sortkey='%08x(sz)'>%,d(sz)</td> |
| 855 | @ <td align="right" data-sortkey='%08x(csz)'>%,d(csz)</td> |
| 856 | @ </tr> |
| 857 | } |
| 858 | @ </tbody></table> |
| 859 | db_finalize(&q); |
| 860 | style_table_sorter(); |
| 861 | } |
| 862 | style_finish_page(); |
| 863 | } |
| 864 | |
| 865 | /* |
| 866 | ** WEBPAGE: emailoutq |
| 867 | ** |
| 868 | ** This page, accessible only to administrators, allows easy viewing of |
| 869 | ** the emailoutq table - the table that contains the email messages |
| 870 | ** that are queued for transmission via SMTP. |
| 871 | */ |
| 872 | void webmail_emailoutq_page(void){ |
| 873 | Stmt q; |
| 874 | login_check_credentials(); |
| 875 | if( !g.perm.Setup ){ |
| 876 | login_needed(0); |
| 877 | return; |
| 878 | } |
| 879 | add_content_sql_commands(g.db); |
| 880 | style_set_current_feature("webmail"); |
| 881 | style_header("emailoutq table"); |
| 882 | style_submenu_element("emailblob table","%R/emailblob"); |
| 883 | db_prepare(&q, |
| 884 | "SELECT edomain, efrom, eto, emsgid, " |
| 885 | " datetime(ectime,'unixepoch')," |
| 886 | " datetime(nullif(emtime,0),'unixepoch')," |
| 887 | " ensend, ets" |
| 888 | " FROM emailoutq" |
| 889 | ); |
| 890 | @ <table border="1" cellpadding="5" cellspacing="0" class="sortable" \ |
| 891 | @ data-column-types='tttnttnn'> |
| 892 | @ <thead><tr><th> edomain <th> efrom <th> eto <th> emsgid \ |
| 893 | @ <th> ectime <th> emtime <th> ensend <th> ets </tr></thead><tbody> |
| 894 | while( db_step(&q)==SQLITE_ROW ){ |
| 895 | const char *zDomain = db_column_text(&q, 0); |
| 896 | const char *zFrom = db_column_text(&q, 1); |
| 897 | const char *zTo = db_column_text(&q, 2); |
| 898 | int emsgid = db_column_int(&q, 3); |
| 899 | const char *zCTime = db_column_text(&q, 4); |
| 900 | const char *zMTime = db_column_text(&q, 5); |
| 901 | int ensend = db_column_int(&q, 6); |
| 902 | int ets = db_column_int(&q, 7); |
| 903 | @ <tr> |
| 904 | @ <td>%h(zDomain) |
| 905 | @ <td>%h(zFrom) |
| 906 | @ <td>%h(zTo) |
| 907 | @ <td align="right"><a href="%R/emailblob?id=%d(emsgid)">%d(emsgid)</a> |
| 908 | @ <td>%h(zCTime) |
| 909 | @ <td>%h(zMTime) |
| 910 | @ <td align="right">%d(ensend) |
| 911 | if( ets>0 ){ |
| 912 | @ <td align="right"><a href="%R/emailblob?id=%d(ets)">%d(ets)</a></td> |
| 913 | }else{ |
| 914 | @ <td> </td> |
| 915 | } |
| 916 | } |
| 917 | @ </tbody></table> |
| 918 | db_finalize(&q); |
| 919 | style_table_sorter(); |
| 920 | style_finish_page(); |
| 921 | } |
| --- a/src/webmail.c | |
| +++ b/src/webmail.c | |
| @@ -1,921 +0,0 @@ | |