| | @@ -117,10 +117,33 @@ |
| 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){ |
| | @@ -178,11 +201,17 @@ |
| 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); |
| | @@ -204,16 +233,24 @@ |
| 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 ){ |
| | @@ -224,11 +261,10 @@ |
| 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 | ** |
| | @@ -242,10 +278,29 @@ |
| 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 |
| | @@ -262,15 +317,21 @@ |
| 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); |
| | @@ -303,10 +364,12 @@ |
| 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 | } |
| | @@ -316,11 +379,23 @@ |
| 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 ){ |
| | @@ -328,12 +403,54 @@ |
| 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); |
| | @@ -343,42 +460,45 @@ |
| 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; |
| 353 | | - }else{ |
| 354 | | - style_submenu_element("All", "%R/webmail?all"); |
| 355 | | - if( zUser ){ |
| 356 | | - blob_append_sql(&sql, " WHERE euser=%Q", zUser); |
| 357 | | - }else{ |
| 358 | | - blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin); |
| 359 | | - } |
| 465 | + if( showAll ){ |
| 466 | + style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); |
| 467 | + }else if( zUser!=0 ){ |
| 468 | + style_submenu_element("All Users", "%s", url_render(&url,"user","*",0,0)); |
| 469 | + if( fossil_strcmp(zUser, g.zLogin)!=0 ){ |
| 470 | + style_submenu_element("My Emails", "%s", url_render(&url,"user",0,0,0)); |
| 471 | + } |
| 472 | + if( zUser ){ |
| 473 | + blob_append_sql(&sql, " WHERE euser=%Q", zUser); |
| 474 | + }else{ |
| 475 | + blob_append_sql(&sql, " WHERE euser=%Q", g.zLogin); |
| 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) → %h(zSubject)</a> |
| 497 | + @ <a href="%s(url_render(&url,"id",zId,0,0))">\ |
| 498 | + @ %h(zFrom) → %h(zSubject)</a> |
| 379 | 499 | @ %h(zDate) |
| 380 | 500 | } |
| 381 | 501 | db_finalize(&q); |
| 382 | 502 | @ </ol> |
| 383 | 503 | style_footer(); |
| 384 | 504 | } |
| 385 | 505 | |