Back to index

courier  0.68.2
msg2html.c
Go to the documentation of this file.
00001 #include "config.h"
00002 /*
00003 ** Copyright 2007-2011 Double Precision, Inc.  See COPYING for
00004 ** distribution information.
00005 */
00006 
00007 /*
00008 */
00009 
00010 #include "msg2html.h"
00011 #include "buf.h"
00012 #include "unicode/unicode.h"
00013 #include "numlib/numlib.h"
00014 #include "gpglib/gpglib.h"
00015 #include "cgi/cgi.h"
00016 #include "rfc822/rfc2047.h"
00017 #include "rfc2045/rfc3676parser.h"
00018 #include "md5/md5.h"
00019 
00020 #include "filter.h"
00021 #include "html.h"
00022 
00023 #include <ctype.h>
00024 
00025 static void (*get_known_handler(struct rfc2045 *mime,
00026                             struct msg2html_info *info))
00027        (FILE *, struct rfc2045 *, struct rfc2045id *, struct msg2html_info *);
00028 
00029 static void (*get_handler(struct rfc2045 *mime,
00030                        struct msg2html_info *info))
00031        (FILE *, struct rfc2045 *,
00032         struct rfc2045id *,
00033         struct msg2html_info *);
00034 
00035 static void addbuf(int c, char **buf, size_t *bufsize, size_t *buflen)
00036 {
00037        if (*buflen == *bufsize)
00038        {
00039               char   *newbuf= *buf ?
00040                      realloc(*buf, *bufsize+512):malloc(*bufsize+512);
00041 
00042               if (!newbuf)
00043                      return;
00044               *buf=newbuf;
00045               *bufsize += 512;
00046        }
00047        (*buf)[(*buflen)++]=c;
00048 }
00049 
00050 static char *get_next_header(FILE *fp, char **value,
00051                           int preserve_nl,
00052                           off_t *mimepos, const off_t *endpos)
00053 {
00054        int    c;
00055        int    eatspaces=0;
00056 
00057        size_t bufsize=256;
00058        char *buf=malloc(bufsize);
00059        size_t buflen=0;
00060 
00061        if (!buf)
00062               return NULL;
00063 
00064        if (mimepos && *mimepos >= *endpos)       return (NULL);
00065 
00066        while (mimepos == 0 || *mimepos < *endpos)
00067        {
00068               if ((c=getc(fp)) != '\n' && c >= 0)
00069               {
00070                      if (c != ' ' && c != '\t' && c != '\r')
00071                             eatspaces=0;
00072 
00073                      if (!eatspaces)
00074                             addbuf(c, &buf, &bufsize, &buflen);
00075                      if (mimepos)  ++ *mimepos;
00076                      continue;
00077               }
00078               if ( c == '\n' && mimepos)  ++ *mimepos;
00079               if (buflen == 0)
00080               {
00081                      free(buf);
00082                      return (0);
00083               }
00084               if (c < 0)    break;
00085               c=getc(fp);
00086               if (c >= 0)   ungetc(c, fp);
00087               if (c < 0 || c == '\n' || !isspace(c))    break;
00088               addbuf(preserve_nl ? '\n':' ', &buf, &bufsize, &buflen);
00089               if (!preserve_nl)
00090                      eatspaces=1;
00091        }
00092        addbuf(0, &buf, &bufsize, &buflen);
00093        buf[buflen-1]=0;  /* Make sure, in outofmem situations */
00094 
00095        for ( *value=buf; **value; (*value)++)
00096        {
00097               if (**value == ':')
00098               {
00099                      **value='\0';
00100                      ++*value;
00101                      break;
00102               }
00103               **value=tolower(**value);
00104        }
00105        while (**value && isspace((int)(unsigned char)**value)) ++*value;
00106        return(buf);
00107 }
00108 
00109 struct msg2html_info *msg2html_alloc(const char *charset)
00110 {
00111        struct msg2html_info *p=malloc(sizeof(struct msg2html_info));
00112 
00113        if (!p)
00114               return NULL;
00115        memset(p, 0, sizeof(*p));
00116 
00117        p->output_character_set=charset;
00118        return p;
00119 }
00120 
00121 void msg2html_add_smiley(struct msg2html_info *i,
00122                       const char *txt, const char *imgurl)
00123 {
00124        char buf[2];
00125        struct msg2html_smiley_list *l;
00126 
00127        buf[0]=*txt;
00128        buf[1]=0;
00129 
00130        if (strlen(i->smiley_index) < sizeof(i->smiley_index)-1)
00131               strcat(i->smiley_index, buf);
00132 
00133 
00134        if ((l=malloc(sizeof(struct msg2html_smiley_list))) != NULL)
00135        {
00136               if ((l->code=strdup(txt)) != NULL)
00137               {
00138                      if ((l->url=strdup(imgurl)) != NULL)
00139                      {
00140                             l->next=i->smileys;
00141                             i->smileys=l;
00142                             return;
00143                      }
00144                      free(l->code);
00145               }
00146               free(l);
00147        }
00148 }
00149 
00150 void msg2html_free(struct msg2html_info *p)
00151 {
00152        struct msg2html_smiley_list *sl;
00153 
00154        while ((sl=p->smileys) != NULL)
00155        {
00156               p->smileys=sl->next;
00157               free(sl->code);
00158               free(sl->url);
00159               free(sl);
00160        }
00161        free(p);
00162 }
00163 
00164 static void html_escape(const char *p, size_t n)
00165 {
00166        char   buf[10];
00167        const  char *q=p;
00168 
00169        while (n)
00170        {
00171               --n;
00172               if (*p == '<')       strcpy(buf, "&lt;");
00173               else if (*p == '>') strcpy(buf, "&gt;");
00174               else if (*p == '&') strcpy(buf, "&amp;");
00175               else if (*p == ' ') strcpy(buf, "&nbsp;");
00176               else if (*p == '\n') strcpy(buf, "<br />");
00177               else if ((unsigned char)(*p) < ' ')
00178                      sprintf(buf, "&#%d;", (int)(unsigned char)*p);
00179               else
00180               {
00181                      p++;
00182                      continue;
00183               }
00184 
00185               fwrite(q, p-q, 1, stdout);
00186               printf("%s", buf);
00187               p++;
00188               q=p;
00189        }
00190        fwrite(q, p-q, 1, stdout);
00191 }
00192 
00193 /*
00194 ** Consider header name: all lowercase, except the very first character,
00195 ** and the first character after every "-"
00196 */
00197 
00198 static void header_uc(char *h)
00199 {
00200        while (*h)
00201        {
00202               *h=toupper( (int)(unsigned char) *h);
00203               ++h;
00204               while (*h)
00205               {
00206                      *h=tolower((int)(unsigned char) *h);
00207                      if (*h++ == '-')     break;
00208               }
00209        }
00210 }
00211 
00212 static void show_email_header(const char *h)
00213 {
00214        html_escape(h, strlen(h));
00215 }
00216 
00217 static void print_header_uc(struct msg2html_info *info, char *h)
00218 {      
00219        header_uc(h);
00220 
00221        printf("<tr valign=\"baseline\"><th align=\"right\" class=\"message-rfc822-header-name\">");
00222 
00223        if (info->email_header)
00224               (*info->email_header)(h, show_email_header);
00225        else
00226               show_email_header(h);
00227        printf(":<span class=\"tt\">&nbsp;</span></th>");
00228 
00229 }
00230 
00231 struct showaddrinfo {
00232        struct msg2html_info *info;
00233        struct rfc822a *a;
00234        int curindex;
00235        int isfirstchar;
00236 } ;
00237 
00238 static void showaddressheader_printc(char c, void *p)
00239 {
00240        struct showaddrinfo *sai= (struct showaddrinfo *)p;
00241 
00242        if (sai->isfirstchar)
00243        {
00244               char *name=0;
00245               char *addr=0;
00246 
00247               if (sai->curindex < sai->a->naddrs &&
00248                   sai->a->addrs[sai->curindex].tokens)
00249               {
00250                      name=rfc822_display_name_tobuf(sai->a,
00251                                                  sai->curindex,
00252                                                  sai->info->
00253                                                  output_character_set);
00254                      addr=rfc822_display_addr_tobuf(sai->a,
00255                                                  sai->curindex,
00256                                                  sai->info->
00257                                                  output_character_set);
00258               }
00259 
00260               if (sai->info->email_address_start)
00261                      (*sai->info->email_address_start)(name, addr);
00262 
00263               if (addr)
00264                      free(addr);
00265               if (name)
00266                      free(name);
00267 
00268               sai->isfirstchar=0;
00269        }
00270 
00271        html_escape(&c, 1);
00272 }
00273 
00274 static void showaddressheader_printsep(const char *sep, void *p)
00275 {
00276        struct showaddrinfo *sai= (struct showaddrinfo *)p;
00277 
00278        if (sai && !sai->isfirstchar)
00279               printf("</span>");
00280 
00281        if (sai->info->email_address_end)
00282               (*sai->info->email_address_end)();
00283 
00284        if (sai)
00285        {
00286               sai->curindex++;
00287               sai->isfirstchar=1;
00288        }
00289 
00290        printf("%s<span class=\"message-rfc822-header-contents\">", sep);
00291 }
00292 
00293 static void showaddressheader_printsep_plain(const char *sep, void *p)
00294 {
00295        printf("%s", sep);
00296 }
00297 
00298 static void showmsgrfc822_addressheader(struct msg2html_info *info,
00299                                    const char *p)
00300 {
00301        struct rfc822t *rfcp;
00302        struct  rfc822a *rfca;
00303 
00304        struct showaddrinfo sai;
00305 
00306        rfcp=rfc822t_alloc_new(p, NULL, NULL);
00307        if (!rfcp)
00308               return;
00309 
00310        rfca=rfc822a_alloc(rfcp);
00311        if (!rfca)
00312        {
00313               rfc822t_free(rfcp);
00314               return;
00315        }
00316 
00317        sai.info=info;
00318        sai.a=rfca;
00319        sai.curindex=0;
00320        sai.isfirstchar=1;
00321 
00322        rfc2047_print_unicodeaddr(rfca, info->output_character_set,
00323                               showaddressheader_printc,
00324                               showaddressheader_printsep, &sai);
00325        if (!sai.isfirstchar)
00326               showaddressheader_printsep("", &sai);
00327        /* This closes the final </a> */
00328 
00329 
00330        rfc822a_free(rfca);
00331        rfc822t_free(rfcp);
00332 }
00333 
00334 static void showrfc2369_printheader(char c, void *p)
00335 {
00336        p=p;
00337        putchar(c);
00338 }
00339 
00340 struct showmsgrfc2369_buflist {
00341        struct showmsgrfc2369_buflist *next;
00342        char *p;
00343 };
00344 
00345 static void showmsgrfc2369_header(struct msg2html_info *info, const char *p)
00346 {
00347        struct rfc822t *rfcp;
00348        struct  rfc822a *rfca;
00349        int    i;
00350        struct showmsgrfc2369_buflist *buflist=NULL;
00351 
00352        rfcp=rfc822t_alloc_new(p, NULL, NULL);
00353        if (!rfcp)
00354               return;
00355 
00356        rfca=rfc822a_alloc(rfcp);
00357        if (!rfca)
00358        {
00359               rfc822t_free(rfcp);
00360               return;
00361        }
00362 
00363        for (i=0; i<rfca->naddrs; i++)
00364        {
00365               char   *p=rfc822_getaddr(rfca, i);
00366               char   *q=info->get_textlink ?
00367                      (*info->get_textlink)(p, info->arg):NULL;
00368               struct showmsgrfc2369_buflist *next;
00369 
00370               if (q)
00371               {
00372                      next=malloc(sizeof(struct showmsgrfc2369_buflist));
00373 
00374                      if (!next)
00375                      {
00376                             free(q);
00377                             q=NULL;
00378                      }
00379                      else
00380                      {
00381                             next->next=buflist;
00382                             buflist=next;
00383                             next->p=q;
00384                      }
00385               }
00386 
00387               if (q && rfca->addrs[i].tokens)
00388               {
00389                      rfca->addrs[i].tokens->token=0;
00390                      if (*q)
00391                             free(p);
00392                      else
00393                      {
00394                      struct buf b;
00395 
00396                             buf_init(&b);
00397                             free(q);
00398                             for (q=p; *q; q++)
00399                             {
00400                             char   c[2];
00401 
00402                                    switch (*q)   {
00403                                    case '<':
00404                                           buf_cat(&b, "&lt;");
00405                                           break;
00406                                    case '>':
00407                                           buf_cat(&b, "&gt;");
00408                                           break;
00409                                    case '&':
00410                                           buf_cat(&b, "&amp;");
00411                                           break;
00412                                    case ' ':
00413                                           buf_cat(&b, "&nbsp;");
00414                                           break;
00415                                    default:
00416                                           c[1]=0;
00417                                           c[0]=*q;
00418                                           buf_cat(&b, c);
00419                                           break;
00420                                    }
00421                             }
00422                             free(p);
00423                             q=strdup(b.ptr ? b.ptr:"");
00424                             buf_free(&b);
00425                             next->p=q;
00426                      }
00427                      rfca->addrs[i].tokens->ptr=q;
00428                      rfca->addrs[i].tokens->len=q ? strlen(q):0;
00429                      rfca->addrs[i].tokens->next=0;
00430               }
00431               else
00432                      free(p);
00433        }
00434 
00435        rfc822_print(rfca, showrfc2369_printheader,
00436                             showaddressheader_printsep_plain, NULL);
00437 
00438        while (buflist)
00439        {
00440               struct showmsgrfc2369_buflist *next=buflist;
00441 
00442               buflist=next->next;
00443 
00444               free(next->p);
00445               free(next);
00446        }
00447 
00448        rfc822a_free(rfca);
00449        rfc822t_free(rfcp);
00450 }
00451 
00452 static int isaddressheader(const char *header)
00453 {
00454        return (strcmp(header, "to") == 0 ||
00455               strcmp(header, "cc") == 0 ||
00456               strcmp(header, "from") == 0 ||
00457               strcmp(header, "sender") == 0 ||
00458               strcmp(header, "resent-to") == 0 ||
00459               strcmp(header, "resent-cc") == 0 ||
00460               strcmp(header, "reply-to") == 0);
00461 }
00462 
00463 
00464 static void showmsgrfc822_headerp(const char *p, size_t l, void *dummy)
00465 {
00466        if (fwrite(p, l, 1, stdout) != 1)
00467            ; /* ignore */
00468 }
00469 
00470 static int showmsgrfc822_header(const char *output_chset,
00471                             const char *p, const char *chset)
00472 {
00473        struct filter_info info;
00474 
00475        unicode_char *uc;
00476        size_t ucsize;
00477 
00478        int conv_err;
00479 
00480        if (libmail_u_convert_tou_tobuf(p, strlen(p), chset,
00481                                    &uc, &ucsize,
00482                                    &conv_err))
00483        {
00484               conv_err=1;
00485               uc=NULL;
00486        }
00487 
00488        filter_start(&info, output_chset, showmsgrfc822_headerp, NULL);
00489 
00490        if (uc)
00491        {
00492               filter(&info, uc, ucsize);
00493               free(uc);
00494        }
00495        filter_end(&info);
00496 
00497        if (info.conversion_error)
00498               conv_err=1;
00499 
00500        return conv_err;
00501 }
00502 
00503 static void showmsgrfc822_body(FILE *fp, struct rfc2045 *rfc,
00504                             struct rfc2045id *idptr, int flag,
00505                             struct msg2html_info *info)
00506 {
00507 char   *header, *value;
00508 char   *save_subject=0;
00509 char   *save_date=0;
00510 off_t  start_pos, end_pos, start_body;
00511 struct rfc2045id *p, newpart;
00512 off_t  dummy;
00513 off_t  pos;
00514 
00515        rfc2045_mimepos(rfc, &start_pos, &end_pos, &start_body, &dummy, &dummy);
00516        if (fseek(fp, start_pos, SEEK_SET) < 0)
00517               return;
00518 
00519        printf("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"message-rfc822-header\">\n");
00520 
00521        pos=start_pos;
00522        while ((header=get_next_header(fp, &value, 1,
00523               &pos, &start_body)) != 0)
00524        {
00525               if (strcmp(header, "list-help") == 0 ||
00526                      strcmp(header, "list-subscribe") == 0 ||
00527                      strcmp(header, "list-unsubscribe") == 0 ||
00528                      strcmp(header, "list-owner") == 0 ||
00529                      strcmp(header, "list-archive") == 0 ||
00530                      strcmp(header, "list-post") == 0)
00531               {
00532                      print_header_uc(info, header);
00533                      printf("<td><span class=\"message-rfc822-header-contents\">");
00534                      showmsgrfc2369_header(info, value);
00535                      printf("</span></td></tr>\n");
00536                      free(header);
00537                      continue;
00538               }
00539 
00540               if (info->fullheaders)
00541               {
00542                      int    isaddress=isaddressheader(header);
00543 
00544                      print_header_uc(info, header);
00545                      printf("<td><span class=\"message-rfc822-header-contents\">");
00546                      if (isaddress)
00547                             showmsgrfc822_addressheader(info, value);
00548                      else
00549                             showmsgrfc822_header(info->output_character_set,
00550                                                value,
00551                                                "iso-8859-1");
00552                      printf("</span></td></tr>\n");
00553                      free(header);
00554                      continue;
00555               }
00556               if (strcmp(header, "subject") == 0)
00557               {
00558                      if (save_subject)    free(save_subject);
00559 
00560                      save_subject=
00561                             rfc822_display_hdrvalue_tobuf(header, value,
00562                                                        info->output_character_set,
00563                                                        NULL,
00564                                                        NULL);
00565 
00566                      if (!save_subject)
00567                             save_subject=strdup(value);
00568 
00569                      free(header);
00570                      continue;
00571               }
00572               if (strcmp(header, "date") == 0)
00573               {
00574                      if (save_date)       free(save_date);
00575                      save_date=strdup(value);
00576                      free(header);
00577                      continue;
00578               }
00579               if (isaddressheader(header))
00580               {
00581                      print_header_uc(info, header);
00582                      printf("<td><span class=\"message-rfc822-header-contents\">");
00583                      showmsgrfc822_addressheader(info, value);
00584                      printf("</span></td></tr>\n");
00585               }
00586               free(header);
00587        }
00588 
00589        if (save_date)
00590        {
00591               time_t t=rfc822_parsedt(save_date);
00592               struct tm *tmp=t ? localtime(&t):0;
00593               char   date_buf[256];
00594 
00595               if (tmp)
00596               {
00597                      char date_header[10];
00598                      const char *date_fmt="%d %b %Y, %I:%M:%S %p";
00599 
00600                      if (info->email_header_date_fmt)
00601                             date_fmt=(*info->email_header_date_fmt)
00602                                    (date_fmt);
00603 
00604                      strcpy(date_header, "Date");
00605                      print_header_uc(info, date_header);
00606 
00607                      strftime(date_buf, sizeof(date_buf)-1, date_fmt, tmp);
00608                      date_buf[sizeof(date_buf)-1]=0;
00609                      printf("<td><span class=\"message-rfc822-header-contents\">");
00610 
00611                      showmsgrfc822_header(info->output_character_set,
00612                                         date_buf,
00613                                         unicode_default_chset());
00614                      printf("</span></td></tr>\n");
00615               }
00616               free(save_date);
00617        }
00618 
00619        if (save_subject)
00620        {
00621               char subj_header[20];
00622 
00623               strcpy(subj_header, "Subject");
00624               print_header_uc(info, subj_header);
00625 
00626               printf("<td><span class=\"message-rfc822-header-contents\">");
00627               showmsgrfc822_header(info->output_character_set, save_subject,
00628                                  info->output_character_set);
00629               printf("</span></td></tr>\n");
00630               free(save_subject);
00631        }
00632 
00633        if (flag && info->message_rfc822_action)
00634               (*info->message_rfc822_action)(idptr);
00635 
00636        printf("</table>\n<hr width=\"100%%\" />\n");
00637 
00638        if (!flag && info->gpgdir && libmail_gpg_has_gpg(info->gpgdir) == 0
00639            && libmail_gpgmime_has_mimegpg(rfc)
00640            && info->gpg_message_action)
00641               (*info->gpg_message_action)();
00642 
00643        if (!idptr)
00644        {
00645               idptr= &newpart;
00646               p=0;
00647        }
00648        else
00649        {
00650               for (p=idptr; p->next; p=p->next)
00651                      ;
00652               p->next=&newpart;
00653        }
00654        newpart.idnum=1;
00655        newpart.next=0;
00656        (*get_handler(rfc, info))(fp, rfc, idptr, info);
00657        if (p)
00658               p->next=0;
00659 }
00660 
00661 void msg2html(FILE *fp, struct rfc2045 *rfc,
00662              struct msg2html_info *info)
00663 {
00664        if (!info->mimegpgfilename)
00665               info->mimegpgfilename="";
00666 
00667        showmsgrfc822_body(fp, rfc, NULL, 0, info);
00668 }
00669 
00670 static void showmsgrfc822(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
00671                        struct msg2html_info *info)
00672 {
00673        if (rfc->firstpart)
00674               showmsgrfc822_body(fp, rfc->firstpart, id, 1, info);
00675 }
00676 
00677 static void showunknown(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
00678                      struct msg2html_info *info)
00679 {
00680 const char    *content_type, *cn;
00681 const char    *dummy;
00682 off_t start_pos, end_pos, start_body;
00683 off_t dummy2;
00684 char   *content_name;
00685 
00686        id=id;
00687        rfc2045_mimeinfo(rfc, &content_type, &dummy, &dummy);
00688 
00689        /* Punt for image/ MIMEs */
00690 
00691        if (strncmp(content_type, "image/", 6) == 0 &&
00692               (rfc->content_disposition == 0
00693                || strcmp(rfc->content_disposition, "attachment")))
00694        {
00695               if (info->inline_image_action)
00696                      (*info->inline_image_action)(id, content_type,
00697                                                info->arg);
00698               return;
00699        }
00700 
00701        if (rfc2231_udecodeType(rfc, "name",
00702                             info->output_character_set, &content_name)
00703            < 0 &&
00704            rfc2231_udecodeDisposition(rfc, "filename",
00705                                    info->output_character_set,
00706                                    &content_name) < 0)
00707        {
00708               if (content_name)
00709                      free(content_name);
00710               content_name=NULL;
00711        }
00712 
00713        if (!content_name &&
00714            ((cn=rfc2045_getattr(rfc->content_type_attr, "name")) ||
00715             (cn=rfc2045_getattr(rfc->content_disposition_attr,
00716                              "filename"))) &&
00717            strstr(cn, "=?") && strstr(cn, "?="))
00718            /* RFC2047 header encoding (not compliant to RFC2047) */
00719        {
00720               content_name =
00721                      rfc822_display_hdrvalue_tobuf("subject",
00722                                                 cn,
00723                                                 info->
00724                                                 output_character_set,
00725                                                 NULL, NULL);
00726        }
00727 
00728        rfc2045_mimepos(rfc, &start_pos, &end_pos, &start_body,
00729                      &dummy2, &dummy2);
00730 
00731        if (info->unknown_attachment_action)
00732               (*info->unknown_attachment_action)(id, content_type,
00733                                              content_name,
00734                                              end_pos-start_body,
00735                                              info->arg);
00736 
00737 
00738        if (content_name)
00739               free(content_name);
00740 }
00741 
00742 void showmultipartdecoded_start(int status, const char **styleptr)
00743 {
00744        const char *style= status ? "message-gpg-bad":"message-gpg-good";
00745 
00746        printf("<table border=\"0\" cellpadding=\"2\" class=\"%s\"><tr><td>"
00747               "<table border=\"0\" class=\"message-gpg\"><tr><td>", style);
00748        *styleptr=status ? "message-gpg-bad-text":"message-gpg-good-text";
00749 
00750 }
00751 
00752 void showmultipartdecoded_end()
00753 {
00754        printf("</td></tr></table></td></tr></table>\n");
00755 }
00756 
00757 static void showmultipart(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
00758                        struct msg2html_info *info)
00759 {
00760 const char    *content_type, *dummy;
00761 struct rfc2045 *q;
00762 struct rfc2045id     nextpart, nextnextpart;
00763 struct rfc2045id     *p;
00764 int gpg_status;
00765 
00766        for (p=id; p->next; p=p->next)
00767               ;
00768        p->next=&nextpart;
00769        nextpart.idnum=0;
00770        nextpart.next=0;
00771 
00772        rfc2045_mimeinfo(rfc, &content_type, &dummy, &dummy);
00773 
00774        if (info->is_gpg_enabled &&
00775            libmail_gpgmime_is_decoded(rfc, &gpg_status))
00776        {
00777               const char *style;
00778               showmultipartdecoded_start(gpg_status, &style);
00779               for (q=rfc->firstpart; q; q=q->next, ++nextpart.idnum)
00780               {
00781                      if (q->isdummy)      continue;
00782 
00783                      
00784                      if (nextpart.idnum == 1)
00785                      {
00786                             printf("<blockquote class=\"%s\">",
00787                                    style);
00788                      }
00789 
00790                      (*get_handler(q, info))(fp, q, id, info);
00791                      if (nextpart.idnum == 1)
00792                      {
00793                             printf("</blockquote>");
00794                      }
00795                      else
00796                             if (q->next)
00797                                    printf("<hr width=\"100%%\" />\n");
00798               }
00799               showmultipartdecoded_end();
00800        }
00801        else if (strcmp(content_type, "multipart/alternative") == 0)
00802        {
00803               struct rfc2045 *q, *r=0, *s;
00804        int    idnum=0;
00805        int    dummy;
00806 
00807               for (q=rfc->firstpart; q; q=q->next, ++idnum)
00808               {
00809                      int found=0;
00810                      if (q->isdummy)      continue;
00811 
00812                      /*
00813                      ** We pick this multipart/related section if:
00814                      **
00815                      ** 1) This is the first section, or
00816                      ** 2) We know how to display this section, or
00817                      ** 3) It's a multipart/signed section and we know
00818                      **    how to display the signed content.
00819                      ** 4) It's a decoded section, and we know how to
00820                      **    display the decoded section.
00821                      */
00822 
00823                      if (!r)
00824                             found=1;
00825                      else if ((s=libmail_gpgmime_is_multipart_signed(q))
00826                              != 0)
00827                      {
00828                             if (get_known_handler(s, info))
00829                                    found=1;
00830                      }
00831                      else if ( *info->mimegpgfilename
00832                               && libmail_gpgmime_is_decoded(q, &dummy))
00833                      {
00834                             if ((s=libmail_gpgmime_decoded_content(q)) != 0
00835                                 && get_known_handler(s, info))
00836                                    found=1;
00837                      }
00838                      else if (get_known_handler(q, info))
00839                      {
00840                             found=1;
00841                      }
00842 
00843                      if (found)
00844                      {
00845                             r=q;
00846                             nextpart.idnum=idnum;
00847                      }
00848               }
00849 
00850               if (r)
00851                      (*get_handler(r, info))(fp, r, id, info);
00852        }
00853        else if (strcmp(content_type, "multipart/related") == 0)
00854        {
00855        char *sid=rfc2045_related_start(rfc);
00856 
00857               /*
00858               ** We can't just walts in, search for the Content-ID:,
00859               ** and skeddaddle, that's because we need to keep track of
00860               ** our MIME section.  So we pretend that we're multipart/mixed,
00861               ** see below, and abort at the first opportunity.
00862               */
00863 
00864               for (q=rfc->firstpart; q; q=q->next, ++nextpart.idnum)
00865               {
00866               const char *cid;
00867 
00868                      if (q->isdummy)      continue;
00869 
00870                      cid=rfc2045_content_id(q);
00871 
00872                      if (sid && *sid && strcmp(sid, cid))
00873                      {
00874                             struct rfc2045 *qq;
00875 
00876                             qq=libmail_gpgmime_is_multipart_signed(q);
00877 
00878                             if (!qq) continue;
00879 
00880                             /* Don't give up just yet */
00881 
00882                             cid=rfc2045_content_id(qq);
00883 
00884                             if (sid && *sid && strcmp(sid, cid))
00885                             {
00886                                    /* Not yet, check for MIME/GPG stuff */
00887 
00888 
00889 
00890                                    /* Ok, we can give up now */
00891                                    continue;
00892                             }
00893                             nextnextpart.idnum=1;
00894                             nextnextpart.next=0;
00895                             nextpart.next= &nextnextpart;
00896                      }
00897                      (*get_handler(q, info))(fp, q, id, info);
00898 
00899                      break;
00900                      /* In all cases, we stop after dumping something */
00901               }
00902               if (sid)      free(sid);
00903        }
00904        else
00905        {
00906               for (q=rfc->firstpart; q; q=q->next, ++nextpart.idnum)
00907               {
00908                      if (q->isdummy)      continue;
00909                      (*get_handler(q, info))(fp, q, id, info);
00910                      if (q->next)
00911                             printf("<hr width=\"100%%\" />\n");
00912               }
00913        }
00914        p->next=0;
00915 }
00916 
00917 static int text_to_stdout(const char *p, size_t n, void *dummy)
00918 {
00919        while (n)
00920        {
00921               --n;
00922               putchar(*p++);
00923        }
00924        return 0;
00925 }
00926 
00927 static void convert_unicode(const unicode_char *uc,
00928                          size_t n, void *dummy)
00929 {
00930        libmail_u_convert_uc(*(libmail_u_convert_handle_t *)dummy, uc, n);
00931 }
00932 
00933 static int htmlfilter_stub(const char *ptr, size_t cnt, void *voidptr)
00934 {
00935        htmlfilter((struct htmlfilter_info *)voidptr,
00936                  (const unicode_char *)ptr, cnt/sizeof(unicode_char));
00937        return (0);
00938 }
00939 
00940 
00941 /* Recursive search for a Content-ID: header that we want */
00942 
00943 static struct rfc2045 *find_cid(struct rfc2045 *p, const char *cidurl)
00944 {
00945 const char *cid=rfc2045_content_id(p);
00946 
00947        if (cid && strcmp(cid, cidurl) == 0)
00948               return (p);
00949 
00950        for (p=p->firstpart; p; p=p->next)
00951        {
00952        struct rfc2045 *q;
00953 
00954               if (p->isdummy)      continue;
00955 
00956               q=find_cid(p, cidurl);
00957               if (q) return (q);
00958        }
00959        return (0);
00960 }
00961 
00962 /*
00963 ** Given an rfc2045 ptr, return the mime reference that will resolve to
00964 ** this MIME part.
00965 */
00966 
00967 static char *rfc2mimeid(struct rfc2045 *p)
00968 {
00969 char   buf[MAXLONGSIZE+1];
00970 char   *q=0;
00971 unsigned n=p->pindex+1;     /* mime counts start at one */
00972 char   *r;
00973 
00974        if (p->parent)
00975        {
00976               q=rfc2mimeid(p->parent);
00977               if (p->parent->firstpart->isdummy)
00978                      --n;   /* ... except let's ignore the dummy part */
00979        }
00980        else   n=1;
00981 
00982        sprintf(buf, "%u", n);
00983        r=malloc( (q ? strlen(q)+1:0)+strlen(buf)+1);
00984        if (!r)
00985        {
00986               if (q)
00987                      free(q);
00988               return NULL;
00989        }
00990        *r=0;
00991        if (q)
00992        {
00993               strcat(strcat(r, q), ".");
00994               free(q);
00995        }
00996        strcat(r, buf);
00997        return (r);
00998 }
00999 
01000 /*
01001 ** Convert cid: url to a http:// reference that will access the indicated
01002 ** MIME section.
01003 */
01004 
01005 struct convert_cid_info {
01006        struct rfc2045 *rfc;
01007        struct msg2html_info *info;
01008 };
01009 
01010 static void add_decoded_link(struct rfc2045 *, const char *, int);
01011 
01012 static char *convertcid(const char *cidurl, void *voidp)
01013 {
01014        struct convert_cid_info *cid_info=
01015               (struct convert_cid_info *)voidp;
01016 
01017        struct rfc2045 *rfc=cid_info->rfc;
01018        struct rfc2045 *savep;
01019 
01020        char   *mimeid;
01021        char   *p;
01022        char *mimegpgfilename=cgiurlencode(cid_info->info->mimegpgfilename);
01023        int dummy;
01024 
01025        if (!mimegpgfilename)
01026               return NULL;
01027 
01028        if (rfc->parent)     rfc=rfc->parent;
01029        if (rfc->parent)
01030        {
01031               if (libmail_gpgmime_is_multipart_signed(rfc) ||
01032                   (*mimegpgfilename
01033                    && libmail_gpgmime_is_decoded(rfc, &dummy)))
01034                      rfc=rfc->parent;
01035        }
01036 
01037        savep=rfc;
01038        rfc=find_cid(rfc, cidurl);
01039 
01040        if (!rfc)
01041               /* Sometimes broken MS software needs to go one step higher */
01042        {
01043               while ((savep=savep->parent) != NULL)
01044               {
01045                      rfc=find_cid(savep, cidurl);
01046                      if (rfc)
01047                             break;
01048               }
01049        }
01050 
01051        if (!rfc)     /* Not found, punt */
01052        {
01053               free(mimegpgfilename);
01054               return strdup("");
01055        }
01056 
01057        mimeid=rfc2mimeid(rfc);
01058 
01059        if (!mimeid)
01060               p=NULL;
01061        else if (!cid_info->info->get_url_to_mime_part)
01062               p=strdup("");
01063        else
01064               p=(*cid_info->info->get_url_to_mime_part)(mimeid,
01065                                                    cid_info->info);
01066        free(mimeid);
01067 
01068        if (*mimegpgfilename && rfc->parent &&
01069            libmail_gpgmime_is_decoded(rfc->parent, &dummy))
01070               add_decoded_link(rfc->parent, mimeid, dummy);
01071 
01072        return p;
01073 }
01074 
01075 /*
01076 ** When we output a multipart/related link to some content that has been
01077 ** signed/encoded, we save the decoding status, for later.
01078 **
01079 ** Note -- we collapse multiple links to the same content.
01080 */
01081 
01082 static struct decoded_list {
01083        struct decoded_list *next;
01084        struct rfc2045 *ptr;
01085        char *mimeid;
01086        int status;
01087 } *decoded_first=0, *decoded_last=0;
01088 
01089 static void add_decoded_link(struct rfc2045 *ptr, const char *mimeid,
01090                           int status)
01091 {
01092        struct decoded_list *p;
01093 
01094        for (p=decoded_first; p; p=p->next)
01095        {
01096 
01097               if (strcmp(p->mimeid, mimeid) == 0)
01098                      return;       /* Dupe */
01099        }
01100 
01101        p=(struct decoded_list *)malloc(sizeof(*p));
01102 
01103        if (!p)
01104               return;
01105 
01106        p->mimeid=strdup(mimeid);
01107 
01108        if (!p->mimeid)
01109        {
01110               free(p);
01111               return;
01112        }
01113        p->next=0;
01114 
01115        if (decoded_last)
01116               decoded_last->next=p;
01117        else
01118               decoded_first=p;
01119 
01120        decoded_last=p;
01121 
01122        p->ptr=ptr;
01123        p->status=status;
01124 }
01125 
01126 static void showtexthtml(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
01127                       struct msg2html_info *info)
01128 {
01129        char   *content_base;
01130        const char *mime_charset, *dummy_s;
01131 
01132        struct htmlfilter_info *hf_info;
01133        libmail_u_convert_handle_t h;
01134 
01135        id=id;
01136 
01137 
01138        content_base=rfc2045_content_base(rfc);
01139 
01140        if (!content_base)
01141               return;
01142 
01143        rfc2045_mimeinfo(rfc, &dummy_s, &dummy_s, &mime_charset);
01144 
01145        h=libmail_u_convert_init(libmail_u_ucs4_native,
01146                              info->output_character_set,
01147                              text_to_stdout, NULL);
01148 
01149        if (!h)
01150               hf_info=NULL;
01151        else
01152               hf_info=htmlfilter_alloc(&convert_unicode, &h);
01153 
01154        if (hf_info)
01155        {
01156               struct rfc2045src *src;
01157               struct convert_cid_info cid_info;
01158 
01159               cid_info.rfc=rfc;
01160               cid_info.info=info;
01161 
01162 #if 0
01163        {
01164               FILE *fp=fopen("/tmp/pid", "w");
01165 
01166               if (fp)
01167               {
01168                      fprintf(fp, "%d", (int)getpid());
01169                      fclose(fp);
01170                      sleep(10);
01171               }
01172        }
01173 #endif
01174 
01175               htmlfilter_set_http_prefix(hf_info, info->wash_http_prefix);
01176               htmlfilter_set_convertcid(hf_info, &convertcid, &cid_info);
01177 
01178               htmlfilter_set_contentbase(hf_info, content_base);
01179 
01180               htmlfilter_set_mailto_prefix(hf_info, info->wash_mailto_prefix);
01181 
01182               if (info->html_content_follows)
01183                      (*info->html_content_follows)();
01184 
01185               printf("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%%\"><tr><td>\n");
01186 
01187               src=rfc2045src_init_fd(fileno(fp));
01188 
01189               if (src)
01190               {
01191                      int conv_err;
01192 
01193                      rfc2045_decodetextmimesection(src, rfc,
01194                                                 libmail_u_ucs4_native,
01195                                                 &conv_err,
01196                                                 &htmlfilter_stub,
01197                                                 hf_info);
01198                      rfc2045src_deinit(src);
01199 
01200                      if (conv_err && info->charset_warning)
01201                             (*info->charset_warning)(mime_charset,
01202                                                   info->arg);
01203               }
01204 
01205               htmlfilter_free(hf_info);
01206               libmail_u_convert_deinit(h, NULL);
01207               printf("</td></tr>");
01208        }
01209 
01210        free(content_base);
01211 
01212        while (decoded_first)
01213        {
01214               struct decoded_list *p=decoded_first;
01215               const char *style;
01216 
01217               struct rfc2045 *q;
01218 
01219               printf("<tr><td>");
01220 
01221               showmultipartdecoded_start(p->status, &style);
01222 
01223               for (q=p->ptr->firstpart; q; q=q->next)
01224               {
01225                      if (q->isdummy)
01226                             continue;
01227 
01228                      printf("<div class=\"%s\">", style);
01229                      (*get_handler(q, info))(fp, q, NULL, info);
01230                      printf("</div>\n");
01231                      break;
01232               }
01233               showmultipartdecoded_end();
01234               decoded_first=p->next;
01235               free(p->mimeid);
01236               free(p);
01237               printf("</td></tr>\n");
01238        }
01239        printf("</table>\n");
01240 
01241 }
01242 
01243 static void showdsn(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
01244                   struct msg2html_info *info)
01245 {
01246 off_t  start_pos, end_pos, start_body;
01247 off_t  dummy;
01248 
01249        id=id;
01250        rfc2045_mimepos(rfc, &start_pos, &end_pos, &start_body, &dummy, &dummy);
01251        if (fseek(fp, start_body, SEEK_SET) < 0)
01252        {
01253               printf("Seek error.");
01254               return;
01255        }
01256        printf("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n");
01257        while (start_body < end_pos)
01258        {
01259        int    c=getc(fp);
01260        char   *header, *value;
01261 
01262               if (c == EOF) break;
01263               if (c == '\n')
01264               {
01265                      printf("<tr><td colspan=\"2\"><hr /></td></tr>\n");
01266                      ++start_body;
01267                      continue;
01268               }
01269               ungetc(c, fp);
01270 
01271               if ((header=get_next_header(fp, &value, 1,
01272                      &start_body, &end_pos)) == 0)
01273                      break;
01274 
01275               print_header_uc(info, header);
01276               printf("<td><span class=\"message-rfc822-header-contents\">");
01277               /* showmsgrfc822_addressheader(value); */
01278               printf("%s", value);
01279               printf("</span></td></tr>\n");
01280               free(header);
01281        }
01282        printf("</table>\n");
01283 }
01284 
01285 static const char validurlchars[]=
01286        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
01287        ":/.~%+?&#=@;-_,";
01288 
01289 const char *skip_text_url(const char *r, const char *end)
01290 {
01291        const char *q=r;
01292 
01293        for (; r < end && strchr(validurlchars, *r); r++)
01294        {
01295               if (*r == '&' && (end-r < 5 || strncmp(r, "&amp;", 5)))
01296                      break;
01297        }
01298        if (r > q && (r[-1] == ',' || r[-1] == '.' || r[-1] == ';'))   --r;
01299        return (r);
01300 }
01301 
01302 /*
01303 ** The plain text HTML formatter renders text/plain content as HTML.
01304 **
01305 ** The following structure maintains state of the HTML formatter.
01306 */
01307 
01308 struct msg2html_textplain_info {
01309 
01310        /*
01311        ** RFC 3676 parser of the raw text/plain content.
01312        */
01313        rfc3676_parser_t parser;
01314 
01315        /*
01316        ** This layer also needs to know whether the raw format is flowed
01317        ** format.
01318        */
01319        int flowed;
01320 
01321        int conv_err; /* A transcoding error has occured */
01322 
01323        /*
01324        ** Optionally replace smiley sequences with image URLs
01325        */
01326 
01327        const char *smiley_index; /* First character in all smiley seqs */
01328        struct msg2html_smiley_list *smileys; /* All smiley seqs */
01329 
01330        /*
01331        ** Flag - convert text/plain content to HTML using wiki-style
01332        ** formatting codes. Implies flowed format, as well.
01333        */
01334        int wikifmt;
01335 
01336        /*
01337        ** Whether a paragraph is now open. Only used with flowed format.
01338        */
01339 
01340        int paragraph_open;
01341 
01342        /*
01343        ** Whether the <LI> tag is open, when doing wiki-style formatting.
01344        */
01345        int li_open;
01346 
01347        /*
01348        ** Quotation level of flowed format line.
01349        */
01350        size_t cur_quote_level;
01351 
01352        /*
01353        ** Whether this line's quotation level is different than the previous
01354        ** line's.
01355        */
01356        size_t quote_level_has_changed;
01357 
01358        /*
01359        ** Whether process_text() is getting invoked at the start of the
01360        ** line.
01361        */
01362        int start_of_line;
01363 
01364        /* wikifmt settings */
01365 
01366        int ttline; /* Line begun with a space, <tt> is now open */
01367 
01368        int text_decor_state;       /* Future text should have these decorations */
01369        int text_decor_state_cur; /* Current decorations in place */
01370 
01371        int text_decor_apostrophe_cnt; /* Apostrophe accumulator */
01372 
01373        unicode_char text_decor_uline_prev;
01374        /* Previous character, used when scanning for underline enable */
01375 
01376        /*
01377        ** Although we're getting the parsed text incrementally, with no
01378        ** guarantees how many characters in each chunk, we want to accumulate
01379        ** some context at the beginning of the line in order to be able to
01380        ** handle wikifmt codes.
01381        */
01382 
01383        unicode_char lookahead_buf[64];
01384        size_t lookahead_saved;
01385 
01386        /*
01387        ** Current list level
01388        */
01389        char current_list_level[16];
01390 
01391        /*
01392        ** Close paragraph, </p> or </hX>
01393        */
01394 
01395        char paragraph_close[8];
01396 
01397        /*
01398        ** Handler that searches for http/https/mailto URLs in plain text and
01399        ** highlights them.
01400        */
01401        size_t (*text_url_handler)(struct msg2html_textplain_info *,
01402                                const unicode_char *,
01403                                size_t);
01404 
01405        /*
01406        ** Output filter for unescaped text. Replaces HTML codes.
01407        */
01408        struct filter_info info;
01409 
01410        /*
01411        ** A URL being accumulated.
01412        */
01413        char urlbuf[8192];
01414        size_t urlindex;
01415 
01416        /*
01417        ** Caller-provided function to take a URL and return an HTML
01418        ** sequence to display the URL.
01419        **
01420        ** The characters in the provided URLs come from validurlchars, and
01421        ** are "safe".
01422        **
01423        ** The caller returns a malloced buffer, or NULL.
01424        */
01425        char *(*get_textlink)(const char *url, void *arg);
01426        void *get_textlink_arg;
01427 };
01428 
01429 /*
01430 ** Convenience function. Accumulated latin chars that should be generated
01431 ** without escaping them.
01432 */
01433 static void text_emit_passthru(struct msg2html_textplain_info *info,
01434                             const char *str)
01435 {
01436        while (*str)
01437        {
01438               unicode_char ch=(unsigned char)*str++;
01439 
01440               filter_passthru(&info->info, &ch, 1);
01441        }
01442 }
01443 
01444 /* If there's an open paragraph at this time, it needs to be closed */
01445 
01446 static void text_close_paragraph(struct msg2html_textplain_info *info)
01447 {
01448        if (info->paragraph_open)
01449        {
01450               unicode_char uc='\n';
01451 
01452               info->paragraph_open=0;
01453               text_emit_passthru(info, info->paragraph_close);
01454               filter(&info->info, &uc, 1);
01455        }
01456 }
01457 
01458 /* Need to make sure an <LI> tag is open at this time */
01459 
01460 static void text_open_li(struct msg2html_textplain_info *info)
01461 {
01462        if (!info->li_open)
01463        {
01464               text_emit_passthru(info, "<li>");
01465               info->li_open=1;
01466        }
01467 }
01468 
01469 /* Need to make sure that an <LI> tag is now closed */
01470 
01471 static void text_close_li(struct msg2html_textplain_info *info)
01472 {
01473        text_close_paragraph(info);
01474 
01475        if (info->li_open)
01476        {
01477               unicode_char uc='\n';
01478 
01479               info->li_open=0;
01480               text_emit_passthru(info, "</li>");
01481               filter(&info->info, &uc, 1);
01482        }
01483 }
01484 
01485 /* Opening tag for a list */
01486 static const char *text_list_open_tag(char ch)
01487 {
01488        return ch == '#' ? "<ol>":"<ul>";
01489 }
01490 
01491 /* Closing tag for a list */
01492 static const char *text_list_close_tag(char ch)
01493 {
01494        return ch == '#' ? "</ol>":"</ul>";
01495 }
01496 
01497 /*
01498 ** A list level is specified. Open or close list tags, in order to achieve
01499 ** that. Take into account the existing level, and issue the appropriate
01500 ** HTML to result in the given list level being open.
01501 */
01502 static int text_set_list_level(struct msg2html_textplain_info *info,
01503                             const char *new_level,
01504                             size_t nl)
01505 {
01506        size_t pl=strlen(info->current_list_level);
01507        int list_level_changed=0;
01508 
01509        if (nl > sizeof(info->current_list_level)-1)
01510               nl=sizeof(info->current_list_level)-1;
01511 
01512        /*
01513        ** If there's a nesting mismatch, keep closing until we find a matching
01514        ** level prefix.
01515        */
01516 
01517        while (pl &&
01518               (pl > nl || memcmp(info->current_list_level, new_level, pl)))
01519        {
01520               text_close_li(info);
01521               text_emit_passthru(info,
01522                                text_list_close_tag(info->current_list_level
01523                                                  [--pl]));
01524 
01525               list_level_changed=1;
01526               if (pl > 0)
01527                      info->li_open=1;
01528               /*
01529               ** Nested lists always begin with <LI> being open, so restore
01530               ** the LI open state.
01531               */
01532        }
01533 
01534        while (pl < nl)
01535        {
01536               text_close_paragraph(info);
01537 
01538               /* an <LI> must be open before opening a nested list */
01539 
01540               if (pl > 0)
01541                      text_open_li(info);
01542               text_emit_passthru(info, text_list_open_tag(new_level[pl]));
01543               ++pl;
01544               list_level_changed=1;
01545               info->li_open=0; /* No LI is currently in place */
01546        }
01547 
01548        memcpy(info->current_list_level, new_level, nl);
01549        info->current_list_level[nl]=0;
01550 
01551        return list_level_changed;
01552 }
01553 
01554 /*
01555 ** The next flowed format line has the indicated quote level. Issue
01556 ** appropriate HTML to close or open BLOCKQUOTE tags, as required.
01557 */
01558 static void text_set_quote_level(struct msg2html_textplain_info *info,
01559                              size_t new_quote_level)
01560 {
01561        info->quote_level_has_changed=0;
01562 
01563        /*
01564        ** When formatting flowed text, need to stop any open lists before
01565        ** entering quoted content.
01566        */
01567 
01568        if (info->flowed && info->cur_quote_level != new_quote_level)
01569        {
01570               text_set_list_level(info, "", 0);
01571 
01572               text_close_paragraph(info);
01573               info->quote_level_has_changed=1;
01574        }
01575 
01576        while (info->cur_quote_level < new_quote_level)
01577        {
01578               char str[160];
01579 
01580               sprintf(str, info->wikifmt ?
01581                      "\n<blockquote type=\"cite\">":
01582                      "\n<blockquote type=\"cite\" class=\"cite%d\">"
01583                      "<div class=\"quotedtext\">",
01584                      (int)(info->cur_quote_level % 3));
01585 
01586               text_emit_passthru(info, str);
01587               ++info->cur_quote_level;
01588        }
01589 
01590        while (info->cur_quote_level > new_quote_level)
01591        {
01592               text_emit_passthru(info,
01593                                info->wikifmt ?
01594                                "</blockquote>\n":
01595                                "</div></blockquote>\n");
01596               --info->cur_quote_level;
01597        }
01598 }
01599 
01600 static void text_process_decor_begin(struct msg2html_textplain_info *ptr);
01601 static void text_process_decor_end(struct msg2html_textplain_info *ptr);
01602 
01603 static size_t text_contents_notalpha(struct msg2html_textplain_info *ptr,
01604                                  const unicode_char *txt,
01605                                  size_t txt_size);
01606 
01607 static void text_line_contents_with_lookahead(const unicode_char *txt,
01608                                          size_t txt_size,
01609                                          struct msg2html_textplain_info
01610                                          *info);
01611 static void process_text(const unicode_char *txt,
01612                       size_t txt_size,
01613                       struct msg2html_textplain_info *info);
01614 
01615 /*****************************************************************************/
01616 
01617 /*
01618 ** RFC 3676 text processing layer. The raw text/plain encoding gets parsed
01619 ** using the rfc3676 parsing API, which invokes the following callbacks that
01620 ** define a logical line.
01621 **
01622 ** Text at the beginning of the line gets accumulated into a lookahead buffer
01623 ** until there's sufficient amount of text to parse any wiki-formatting codes
01624 ** that are present at the beginning of the line.
01625 **
01626 ** The contents of the logical line are passed to process_text().
01627 ** start_of_line gets set when process_text() is invoked for the first time,
01628 ** at the beginning of the logical line.
01629 */
01630 
01631 static int do_text_line_contents(const unicode_char *txt,
01632                              size_t txt_size,
01633                              void *arg);
01634 
01635 /*
01636 ** Start of a new logical line.
01637 */
01638 static int text_line_begin(size_t quote_level,
01639                         void *arg)
01640 {
01641        struct msg2html_textplain_info *info=
01642               (struct msg2html_textplain_info *)arg;
01643 
01644        /*
01645        ** Process the logical line's quoting level.
01646        */
01647        text_set_quote_level(info, quote_level);
01648 
01649        /*
01650        ** Initialize the lookahead mid-layer.
01651        */
01652        info->lookahead_saved=0;
01653        info->start_of_line=1;
01654 
01655        /* Initialize the decoration layer */
01656 
01657        info->ttline=0;
01658        text_process_decor_begin(info);
01659 
01660        /*
01661        ** Initialize URL collection layer.
01662        */
01663        info->text_url_handler=text_contents_notalpha;
01664        return 0;
01665 }
01666 
01667 /*
01668 ** Process the contents of a logical line.
01669 */
01670 
01671 static int text_line_contents(const unicode_char *txt,
01672                            size_t txt_size,
01673                            void *arg)
01674 {
01675 #if 1
01676        return do_text_line_contents(txt, txt_size, arg);
01677 #else
01678        /* For debugging purposes */
01679 
01680        while (txt_size)
01681        {
01682               do_text_line_contents(txt, 1, arg);
01683               ++txt;
01684               --txt_size;
01685        }
01686 #endif
01687 }
01688 
01689 static int do_text_line_contents(const unicode_char *txt,
01690                              size_t txt_size,
01691                              void *arg)
01692 {
01693        struct msg2html_textplain_info *info=
01694               (struct msg2html_textplain_info *)arg;
01695        unicode_char lookahead_cpy_buf[sizeof(info->lookahead_buf)
01696                                    /sizeof(info->lookahead_buf[0])];
01697        size_t n;
01698 
01699        /*
01700        ** Prepend any saved lookahead data to the new unicode stream.
01701        */
01702 
01703        while (txt_size)
01704        {
01705               if (info->lookahead_saved == 0)
01706               {
01707                      /*
01708                      ** Nothing saved from the last go-around, we can
01709                      ** pass this off to the lookahead mid-layer.
01710                      */
01711 
01712                      text_line_contents_with_lookahead(txt, txt_size, info);
01713                      break;
01714               }
01715 
01716               /*
01717               ** Use as much as can be taken from the new unicode chunk.
01718               **
01719               ** text_line_contents_with_lookahead makes sure that
01720               ** lookahead_saved is not larger than half the buffer size.
01721               */
01722               n=sizeof(lookahead_cpy_buf)/sizeof(lookahead_cpy_buf[0])
01723                      - info->lookahead_saved;
01724 
01725               if (n > txt_size)
01726                      n=txt_size;
01727 
01728               memcpy(lookahead_cpy_buf,
01729                      info->lookahead_buf,
01730                      info->lookahead_saved*sizeof(lookahead_cpy_buf[0]));
01731 
01732               memcpy(lookahead_cpy_buf+info->lookahead_saved,
01733                      txt, n*sizeof(lookahead_cpy_buf[0]));
01734 
01735               text_line_contents_with_lookahead(lookahead_cpy_buf,
01736                                             info->lookahead_saved + n,
01737                                             info);
01738 
01739               txt += n;
01740               txt_size -= n;
01741        }
01742 
01743        return 0;
01744 }
01745 
01746 /*
01747 ** Lookahead accumulator mid-layer. Accumulates line content into
01748 ** lookahead_buf. Next time, the accumulated line content gets resubmitted
01749 ** to this function, prepended to any new content.
01750 */
01751 
01752 static void text_line_contents_with_lookahead(const unicode_char *txt,
01753                                          size_t txt_size,
01754                                          struct msg2html_textplain_info
01755                                          *info)
01756 {
01757        size_t i;
01758 
01759        info->lookahead_saved=0;
01760 
01761 
01762        /*
01763        ** At the beginning of the line, if using wiki markups, make sure
01764        ** there's enough stuff buffered for the main logic to do its work.
01765        */
01766 
01767        if (info->flowed && info->start_of_line && info->wikifmt)
01768        {
01769               for (i=0; i<txt_size; ++i)
01770               {
01771                      switch ((unsigned char)txt[i]) {
01772                      case '#':
01773                      case '*':
01774                      case '=':
01775                             continue;
01776                      default:
01777                             break;
01778                      }
01779                      break;
01780               }
01781 
01782               if (i == txt_size && i <
01783                   sizeof(info->lookahead_buf)
01784                   /sizeof(info->lookahead_buf[0])/2)
01785               {
01786                      info->lookahead_saved=i;
01787                      memcpy(info->lookahead_buf, txt,
01788                             i*sizeof(info->lookahead_buf[0]));
01789                      return;
01790               }
01791        }
01792 
01793        /*
01794        ** In the rest of the line, look for smileys.
01795        */
01796 
01797        while (txt_size)
01798        {
01799               struct msg2html_smiley_list *l;
01800               int flag=0;
01801 
01802               /* Look for the first char that might be a smiley */
01803 
01804               for (i=0; i<txt_size; ++i)
01805               {
01806                      if ((unsigned char)txt[i] == txt[i] &&
01807                          info->smiley_index &&
01808                          strchr(info->smiley_index, txt[i]))
01809                             break;
01810               }
01811 
01812               if (i)
01813               {
01814                      process_text(txt, i, info);
01815                      txt += i;
01816                      txt_size -= i;
01817                      continue;
01818               }
01819 
01820               /*
01821               ** Ok, now figure out if this is a smiley.
01822               */
01823 
01824               for (l=info->smileys; l; l=l->next)
01825               {
01826                      size_t j;
01827 
01828                      if (strlen(l->code) > txt_size)
01829                      {
01830                             flag=1;
01831                             continue; /* Not enough context */
01832                      }
01833 
01834                      for (j=0; l->code[j]; j++)
01835                      {
01836                             if ( (unsigned char)txt[j] != txt[j])
01837                                    break;
01838 
01839                             if ((unsigned char)txt[j] !=
01840                                 (unsigned char)l->code[j])
01841                                    break;
01842                      }
01843 
01844                      if (l->code[j] == 0)
01845                      {
01846                             process_text(txt, 0, info);
01847                             /* May be needed to start a paragraph */
01848 
01849                             text_emit_passthru(info, l->url);
01850 
01851                             txt += j;
01852                             txt_size -= j;
01853                             break;
01854                      }
01855               }
01856 
01857               if (l) /* A smiley was found */
01858                      continue;
01859 
01860               if (flag) /* Insufficient context */
01861               {
01862                      i=txt_size;
01863 
01864                      if (i > sizeof(info->lookahead_buf)
01865                          /sizeof(info->lookahead_buf[0])/2)
01866                      {
01867                             /*
01868                             ** Internal breakage, lookahead buffer
01869                             ** not big enough for the smiley.
01870                             */
01871 
01872                             process_text(txt, txt_size, info);
01873                             break;
01874                      }
01875                      info->lookahead_saved=i;
01876                      memcpy(info->lookahead_buf, txt,
01877                             i*sizeof(info->lookahead_buf[0]));
01878                      break;
01879               }
01880 
01881               /* Did not find a smiley, consume one character */
01882 
01883               process_text(txt, 1, info);
01884               ++txt;
01885               --txt_size;
01886        }
01887 }
01888 
01889 /*
01890 ** Don't want to generate long HTML without line breaks. It's OK to
01891 ** have a linebreak here.
01892 */
01893 static int text_line_flowed_notify(void *arg)
01894 {
01895        unicode_char nl='\n';
01896        struct msg2html_textplain_info *info=
01897               (struct msg2html_textplain_info *)arg;
01898        filter(&info->info, &nl, 1);
01899        return 0;
01900 }
01901 
01902 /*
01903 ** End of the line. Wrap up all the layers.
01904 */
01905 static int text_line_end(void *arg)
01906 {
01907        struct msg2html_textplain_info *info=
01908               (struct msg2html_textplain_info *)arg;
01909 
01910        /*
01911        ** Wrap up the lookahead mid-layer.
01912        */
01913        if (info->lookahead_saved)
01914               process_text(info->lookahead_buf,
01915                           info->lookahead_saved, info);
01916 
01917        /*
01918        ** Wrap up the URL collection layer.
01919        */
01920        (*info->text_url_handler)(info, NULL, 0);
01921 
01922        /*
01923        ** Wrap up the text decoration layer
01924        */
01925        text_process_decor_end(info);
01926 
01927        if (info->flowed)
01928        {
01929               unicode_char uc='\n';
01930 
01931               if (info->start_of_line)
01932               {
01933                      /*
01934                      ** This was an empty line.
01935                      */
01936 
01937                      if (info->paragraph_open)
01938                      {
01939                             /*
01940                             ** A paragraph was open, so this empty line
01941                             ** marks the end of the paragraph.
01942                             */
01943                             text_close_paragraph(info);
01944                             filter(&info->info, &uc, 1);
01945                      }
01946                      else if (!info->quote_level_has_changed)
01947                      {
01948                             /*
01949                             ** In all other cases, an empty line generates
01950                             ** another <br />. However, if the quoting level
01951                             ** has changed, let it slide, because the
01952                             ** forthcoming <p> tag is going to advance
01953                             ** vertical white space.
01954                             */
01955                             text_emit_passthru(info, "<br/>");
01956                             filter(&info->info, &uc, 1);
01957                      }
01958               }
01959               else
01960               {
01961                      /*
01962                      ** Close the open <tt> tag.
01963                      */
01964                      if (info->ttline)
01965                      {
01966                             text_emit_passthru(info, "</tt>");
01967                      }
01968 
01969                      filter(&info->info, &uc, 1);
01970               }
01971               return 0;
01972        }
01973 
01974 
01975        {
01976               unicode_char uc='\n';
01977 
01978               filter(&info->info, &uc, 1);
01979        }
01980        return 0;
01981 }
01982 
01983 static void process_text_wiki(char *paragraph_open,
01984                            const unicode_char **txt_ret,
01985                            size_t *txt_size_ret,
01986                            struct msg2html_textplain_info *info);
01987 
01988 static void process_text(const unicode_char *txt,
01989                       size_t txt_size,
01990                       struct msg2html_textplain_info *info)
01991 {
01992        if (info->flowed && info->start_of_line)
01993        {
01994               unicode_char uc='\n';
01995 
01996               filter(&info->info, &uc, 1);
01997 
01998               /* Starting a logical line */
01999 
02000               if (!info->paragraph_open)
02001               {
02002                      char paragraph_open[8];
02003 
02004                      /*
02005                      ** A paragraph is not open, so open it.
02006                      */
02007 
02008                      strcpy(paragraph_open, "<p>");
02009                      strcpy(info->paragraph_close, "</p>");
02010 
02011                      if (info->wikifmt && info->cur_quote_level == 0)
02012                             process_text_wiki(paragraph_open,
02013                                             &txt, &txt_size, info);
02014 
02015                      text_emit_passthru(info, paragraph_open);
02016                      info->paragraph_open=1;
02017               }
02018               else
02019               {
02020                      /*
02021                      ** Start of a logical line, but not a start of
02022                      ** a paragraph results in an extra <br/>, in the
02023                      ** middle of the existing paragraph.
02024                      */
02025 
02026                      text_emit_passthru(info, "<br/>");
02027               }
02028               if (txt_size && *txt == ' ')
02029                      info->ttline=1;
02030               info->start_of_line=0;
02031 
02032               if (info->ttline)
02033                      text_emit_passthru(info, "<tt class='tt'>");
02034        }
02035 
02036        /*
02037        ** Pass the rest of the text to the URL collection layer.
02038        */
02039        while (txt_size)
02040        {
02041               size_t n= (*info->text_url_handler)(info, txt, txt_size);
02042 
02043               txt += n;
02044               txt_size -= n;
02045        }
02046 }
02047 
02048 
02049 /*
02050 ** Do additional wiki-style HTML formatting.
02051 ** The lookahead mid-layer made sure
02052 ** we have enough context here.
02053 */
02054 
02055 static void process_text_wiki(char *paragraph_open,
02056                            const unicode_char **txt,
02057                            size_t *txt_size,
02058                            struct msg2html_textplain_info *info)
02059 {
02060        size_t i;
02061 
02062        /*
02063        ** "=" at the beginning of the line marks up a heading.
02064        */
02065 
02066        for (i=0; i<*txt_size; i++)
02067               if ((*txt)[i] != '=')
02068                      break;
02069 
02070        if (i > 0)
02071        {
02072               int n=i < 8 ? i:8;
02073 
02074               /* Use <hX> instead of a boring paragraph */
02075 
02076               sprintf(paragraph_open, "<h%d>", n);
02077               sprintf(info->paragraph_close, "</h%d>", n);
02078 
02079               *txt += i;
02080               *txt_size -= i;
02081 
02082               if (*txt_size && **txt == ' ')
02083               {
02084                      ++*txt;
02085                      --*txt_size;
02086               }
02087 
02088               text_set_list_level(info, "", 0);
02089        }
02090        else
02091        {
02092               /*
02093               ** Otherwise, #* characters at the beginning of the line
02094               ** mark up a list.
02095               */
02096 
02097               for (i=0; i<*txt_size; i++)
02098                      if ((*txt)[i] != '#' &&
02099                          (*txt)[i] != '*')
02100                             break;
02101 
02102               if (i > 0)
02103               {
02104                      char new_list_level[sizeof(info->current_list_level)];
02105                      size_t j;
02106                      int rc;
02107 
02108                      for (j=0; j<i; j++)
02109                      {
02110                             if (j >= sizeof(info->current_list_level)-1)
02111                                    break;
02112 
02113                             new_list_level[j]=(*txt)[j];
02114                      }
02115 
02116                      new_list_level[j]=0;
02117 
02118                      rc=text_set_list_level(info, new_list_level, j);
02119 
02120                      *txt += i;
02121                      *txt_size -= i;
02122 
02123                      /*
02124                      ** The same list nesting level prefix followed by +
02125                      ** continues the existing list entry. Otherwise,
02126                      ** a new list entry is started. This is done by
02127                      ** closing the existing list entry, first.
02128                      */
02129 
02130                      if (*txt_size && **txt == '+' && !rc)
02131                      {
02132                             ++*txt;
02133                             --*txt_size;
02134                      }
02135                      else
02136                      {
02137                             text_close_li(info);
02138                      }
02139 
02140                      /* Prepend <li> to <p> in the paragraph open marker */
02141 
02142                      paragraph_open[0]=0;
02143 
02144                      if (!info->li_open)
02145                      {
02146                             strcat(paragraph_open, "<li>");
02147                             info->li_open=1;
02148                      }
02149 
02150                      strcat(paragraph_open, "<p>");
02151 
02152                      if (*txt_size && **txt == ' ')
02153                      {
02154                             ++*txt;
02155                             --*txt_size;
02156                      }
02157               }
02158               else /* Make sure that all lists are now closed */
02159               {
02160                      text_set_list_level(info, "", 0);
02161 
02162                      /*
02163                      ** A space at the beginning of the line generates
02164                      ** a <tt>
02165                      */
02166 
02167                      if (*txt_size && **txt == ' ')
02168                             info->ttline=1;
02169               }
02170        }
02171 }
02172 
02173 /**************************************************************************/
02174 
02175 /*
02176 ** The URL collection layer.
02177 **
02178 ** A potential URL gets accumulated in a buffer, until it's known whether
02179 ** it's a URL or not.
02180 **
02181 ** If it's not a URL, the text is passed to the text decoration layer.
02182 **
02183 ** A NULL pointer passed to the URL handling layer is an end-of-line
02184 ** indication, and anything that's left in the accumulated buffer is
02185 ** passed through to the text decoration layer.
02186 */
02187 
02188 static void text_process_decor(struct msg2html_textplain_info *info,
02189                             const unicode_char *uc,
02190                             size_t cnt);
02191 
02192 static void text_process_decor_uline(struct msg2html_textplain_info *info,
02193                                  const unicode_char *uc,
02194                                  size_t cnt);
02195 
02196 static void text_process_plain(struct msg2html_textplain_info *info,
02197                             const unicode_char *uc,
02198                             size_t cnt);
02199 
02200 static void emit_char_buffer(struct msg2html_textplain_info *info,
02201                           const char *uc,
02202                           size_t cnt,
02203                           void (*func)(struct msg2html_textplain_info *info,
02204                                      const unicode_char *uc,
02205                                      size_t cnt));
02206 
02207 
02208 static size_t text_contents_checkurl(struct msg2html_textplain_info *info,
02209                                  const unicode_char *txt,
02210                                  size_t txt_size);
02211 /*
02212 ** Initial state of the URL collection layer -- processing non-alphabetic
02213 ** content. The non-alphabetic content is passed through to the text
02214 ** decoration layer.
02215 */
02216 
02217 static size_t text_contents_notalpha(struct msg2html_textplain_info *info,
02218                                  const unicode_char *txt,
02219                                  size_t txt_size)
02220 {
02221        size_t i;
02222 
02223        if (!txt)
02224               return 0;
02225 
02226        for (i=0; i<txt_size; i++)
02227        {
02228               if (txt[i] >= 'a' && txt[i] <= 'z')
02229               {
02230                      /*
02231                      ** Seen a first alphabetic character, so begin
02232                      ** collecting a URL candidate.
02233                      */
02234                      info->urlindex=0;
02235                      info->text_url_handler=text_contents_checkurl;
02236                      break;
02237               }
02238        }
02239 
02240        if (i)
02241               text_process_decor(info, txt, i);
02242 
02243        return i;
02244 }
02245 
02246 static size_t text_contents_nourl(struct msg2html_textplain_info *info,
02247                               const unicode_char *txt,
02248                               size_t txt_size);
02249 
02250 static size_t text_contents_collecturl(struct msg2html_textplain_info *info,
02251                                    const unicode_char *txt,
02252                                    size_t txt_size);
02253 /*
02254 ** Collecting what may be a URL method name.
02255 */
02256 static size_t text_contents_checkurl(struct msg2html_textplain_info *info,
02257                                  const unicode_char *txt,
02258                                  size_t txt_size)
02259 {
02260        size_t i;
02261 
02262        if (txt == NULL)
02263        {
02264               /* End of line, flush the buffer */
02265               if (info->urlindex)
02266               {
02267                      emit_char_buffer(info, info->urlbuf,
02268                                     info->urlindex,
02269                                     text_process_decor);
02270                      info->urlindex=0;
02271               }
02272               return 0;
02273        }
02274 
02275        /*
02276        ** Accumulate this content, until notified otherwise.
02277        */
02278 
02279        for (i=0; i<txt_size; i++)
02280        {
02281               if (i+info->urlindex > 32)
02282               {
02283                      /*
02284                      ** Too long, can't be a method name.
02285                      */
02286 
02287                      emit_char_buffer(info, info->urlbuf,
02288                                     info->urlindex,
02289                                     text_process_decor);
02290 
02291                      info->text_url_handler=text_contents_nourl;
02292                      return text_contents_nourl(info, txt, txt_size);
02293               }
02294 
02295               if (txt[i] == ':') /* Bingo? */
02296               {
02297                      info->urlbuf[info->urlindex+i]=0;
02298 
02299                      if (strcmp(info->urlbuf, "http") == 0 ||
02300                          strcmp(info->urlbuf, "https") == 0 ||
02301                          strcmp(info->urlbuf, "mailto") == 0)
02302                      {
02303                             /* Bingo! */
02304                             info->urlbuf[info->urlindex+i]=':';
02305                             ++i;
02306 
02307                             info->urlindex += i;
02308 
02309                             info->text_url_handler=
02310                                    text_contents_collecturl;
02311                             return i;
02312                      }
02313               }
02314 
02315               if (txt[i] < 'a' || txt[i] > 'z')
02316               {
02317                      /* Hit another non-alphabetic character, reset */
02318 
02319                      emit_char_buffer(info, info->urlbuf,
02320                                     info->urlindex+i,
02321                                     text_process_decor);
02322 
02323                      info->text_url_handler=text_contents_notalpha;
02324                      return i;
02325               }
02326 
02327               info->urlbuf[info->urlindex+i]=txt[i];
02328        }
02329 
02330        info->urlindex += i;
02331        return i;
02332 }
02333 
02334 /*
02335 ** Word too long to be a URL, so ignore it.
02336 */
02337 
02338 static size_t text_contents_nourl(struct msg2html_textplain_info *info,
02339                               const unicode_char *txt,
02340                               size_t txt_size)
02341 {
02342        size_t i;
02343 
02344        if (!txt)
02345               return 0;
02346 
02347        for (i=0; i<txt_size; i++)
02348        {
02349               if (txt[i] < 'a' || txt[i] > 'z')
02350               {
02351                      info->text_url_handler=text_contents_notalpha;
02352                      break;
02353               }
02354        }
02355 
02356        if (i)
02357               text_process_decor(info, txt, i);
02358        return i;
02359 }
02360 
02361 /*
02362 ** Call the msg2html user to obtain how the URL should be marked up.
02363 */
02364 static void doemiturl(struct msg2html_textplain_info *info)
02365 {
02366        char *link=info->get_textlink ?
02367               (*info->get_textlink)(info->urlbuf, info->get_textlink_arg):0;
02368 
02369        if (link)
02370        {
02371               text_emit_passthru(info, link);
02372               free(link);
02373               return;
02374        }
02375 
02376        /* Caller doesn't want the URL to be marked up */
02377 
02378        emit_char_buffer(info, info->urlbuf, strlen(info->urlbuf),
02379                       text_process_decor);
02380 }
02381 
02382 static void text_process_decor_apostrophe(struct msg2html_textplain_info *info);
02383 static void set_text_decor(struct msg2html_textplain_info *info, int new_decor);
02384 
02385 
02386 /*
02387 ** Collected a URL. Given that this is text/plain content, if a URL ends
02388 ** with a period, comma, semicolon, or a colon, it shouldn't be a part of the
02389 ** URL, of course.
02390 */
02391 
02392 static void emiturl(struct msg2html_textplain_info *info)
02393 {
02394        size_t url_size=info->urlindex;
02395        char save_char;
02396 
02397        text_process_decor_apostrophe(info);
02398        set_text_decor(info, info->text_decor_state);
02399 
02400        info->text_url_handler=text_contents_notalpha;
02401 
02402        while (url_size > 0)
02403        {
02404               if (strchr(",.;:", info->urlbuf[url_size-1]) == NULL)
02405                      break;
02406               --url_size;
02407        }
02408 
02409        info->urlbuf[info->urlindex]=0;
02410 
02411        save_char=info->urlbuf[url_size];
02412        info->urlbuf[url_size]=0;
02413        doemiturl(info);
02414        info->urlbuf[url_size]=save_char;
02415 
02416        emit_char_buffer(info, info->urlbuf+url_size,
02417                       strlen(info->urlbuf+url_size),
02418                       text_process_decor);
02419 }
02420 
02421 /*
02422 ** Ok, we have a URL, so collect it, then mark it up.
02423 */
02424 static size_t text_contents_collecturl(struct msg2html_textplain_info *info,
02425                                    const unicode_char *txt,
02426                                    size_t txt_size)
02427 {
02428        size_t i;
02429 
02430        if (txt == NULL)
02431        {
02432               emiturl(info);
02433               return 0;
02434        }
02435 
02436        for (i=0; i<txt_size; i++)
02437        {
02438               if (txt[i] < ' ' || txt[i] >= 127 ||
02439                   strchr(validurlchars, txt[i]) == NULL)
02440               {
02441                      emiturl(info);
02442                      break;
02443               }
02444 
02445               if (info->urlindex < sizeof(info->urlbuf)-1)
02446                      info->urlbuf[info->urlindex++]=txt[i];
02447        }
02448 
02449        return i;
02450 }
02451 
02452 /*
02453 ** Convenience function for upconverting chars to unicode chars.
02454 */
02455 
02456 static void emit_char_buffer(struct msg2html_textplain_info *info,
02457                           const char *uc,
02458                           size_t cnt,
02459                           void (*func)(struct msg2html_textplain_info *info,
02460                                      const unicode_char *uc,
02461                                      size_t cnt))
02462 {
02463        unicode_char buf[64];
02464 
02465        while (cnt)
02466        {
02467               size_t n=sizeof(buf)/sizeof(buf[0]);
02468               size_t i;
02469 
02470               if (n > cnt)
02471                      n=cnt;
02472 
02473               for (i=0; i<n; i++)
02474                      buf[i]=(unsigned char)uc[i];
02475 
02476               (*func)(info, buf, i);
02477 
02478               uc += n;
02479               cnt -= n;
02480        }
02481 }
02482 
02483 /****************************************************************************/
02484 
02485 /*
02486 ** The text decoration layer.
02487 **
02488 ** The text decoration layer marks up bold, italic, and underline sequences
02489 ** in a logical line.
02490 */
02491 
02492 #define TEXT_DECOR_B 1
02493 #define TEXT_DECOR_I 2
02494 #define TEXT_DECOR_U 4
02495 
02496 /*
02497 ** Emit markup tags to select the request decoration.
02498 */
02499 static void set_text_decor(struct msg2html_textplain_info *info, int new_decor)
02500 {
02501        if (info->text_decor_state_cur == new_decor)
02502               return; /* Already the right state */
02503 
02504        /*
02505        ** The easiest way to do it is to first turn off old decoration state
02506        ** then turn on the new one.
02507        */
02508 
02509        if (info->text_decor_state_cur & TEXT_DECOR_U)
02510               text_emit_passthru(info, "</u>");
02511 
02512        if (info->text_decor_state_cur & TEXT_DECOR_I)
02513               text_emit_passthru(info, "</i>");
02514 
02515        if (info->text_decor_state_cur & TEXT_DECOR_B)
02516               text_emit_passthru(info, "</b>");
02517 
02518        if (new_decor & TEXT_DECOR_B)
02519               text_emit_passthru(info, "<b>");
02520 
02521        if (new_decor & TEXT_DECOR_I)
02522               text_emit_passthru(info, "<i>");
02523 
02524        if (new_decor & TEXT_DECOR_U)
02525               text_emit_passthru(info, "<u>");
02526 
02527        info->text_decor_state_cur=new_decor;
02528 }
02529 
02530 /*
02531 ** Initialize the decoration layer.
02532 */
02533 
02534 static void text_process_decor_begin(struct msg2html_textplain_info *info)
02535 {
02536        info->text_decor_state=0;
02537        info->text_decor_state_cur=0;
02538        info->text_decor_apostrophe_cnt=0;
02539 
02540        info->text_decor_uline_prev=' ';
02541 }
02542 
02543 /*
02544 ** Process accumulated apostrophes.
02545 */
02546 
02547 static void text_process_decor_apostrophe(struct msg2html_textplain_info *info)
02548 {
02549        unicode_char apos='\'';
02550        int n=info->text_decor_apostrophe_cnt;
02551 
02552        info->text_decor_apostrophe_cnt=0;
02553 
02554        while (n > 0)
02555        {
02556               if (n == 3)
02557               {
02558                      info->text_decor_state ^= TEXT_DECOR_B;
02559                      n -= 3;
02560                      continue;
02561               }
02562 
02563               if (n == 2)
02564               {
02565                      info->text_decor_state ^= TEXT_DECOR_I;
02566                      n -= 2;
02567                      continue;
02568               }
02569 
02570               text_process_decor_uline(info, &apos, 1);
02571               --n;
02572        }
02573 }
02574 
02575 /*
02576 ** Deinitialize the text decoration layer.
02577 */
02578 static void text_process_decor_end(struct msg2html_textplain_info *info)
02579 {
02580        text_process_decor_apostrophe(info);
02581        set_text_decor(info, 0);
02582 }
02583 
02584 /*
02585 ** Process text decorations.
02586 */
02587 static void text_process_decor(struct msg2html_textplain_info *info,
02588                             const unicode_char *uc,
02589                             size_t cnt)
02590 {
02591        size_t i;
02592 
02593        if (!info->wikifmt)
02594        {
02595               /* They are only processed when wiki formatting is requested */
02596               text_process_plain(info, uc, cnt);
02597               return;
02598        }
02599 
02600        /*
02601        ** Look for apostrophes.
02602        */
02603 
02604        while (cnt)
02605        {
02606               if (*uc == '\'')
02607               {
02608                      ++info->text_decor_apostrophe_cnt;
02609                      ++uc;
02610                      --cnt;
02611                      continue;
02612               }
02613 
02614               /*
02615               ** Not an apostrophe right now. Process accumulated apostrophes
02616               ** then look for the next one.
02617               */
02618               text_process_decor_apostrophe(info);
02619 
02620               for (i=0; i<cnt && uc[i] != '\''; ++i)
02621                      ;
02622 
02623               text_process_decor_uline(info, uc, i);
02624 
02625               uc += i;
02626               cnt -= i;
02627        }
02628 }
02629 
02630 /*
02631 ** Text decoration sub-layer for the underline markup.
02632 */
02633 
02634 static void text_process_decor_uline(struct msg2html_textplain_info *info,
02635                                  const unicode_char *uc,
02636                                  size_t cnt)
02637 {
02638        size_t i;
02639        unicode_char space=' ';
02640 
02641        while (cnt)
02642        {
02643               /*
02644               ** When underlining is not turned on, look for a space followed
02645               ** by a _.
02646               */
02647 
02648               if (!(info->text_decor_state & TEXT_DECOR_U))
02649               {
02650                      if (info->text_decor_uline_prev == ' ' && *uc == '_')
02651                      {
02652                             info->text_decor_state |= TEXT_DECOR_U;
02653                             ++uc;
02654                             --cnt;
02655 
02656                             /* Found it */
02657                             continue;
02658                      }
02659 
02660                      /* Look for it */
02661 
02662                      for (i=0; i<cnt; i++)
02663                      {
02664                             if (info->text_decor_uline_prev == ' ' &&
02665                                 uc[i] == '_')
02666                                    break;
02667 
02668                             info->text_decor_uline_prev=uc[i];
02669                      }
02670 
02671                      if (i)
02672                             text_process_plain(info, uc, i);
02673 
02674                      uc += i;
02675                      cnt -= i;
02676                      continue;
02677               }
02678 
02679               /*
02680               ** Underlining is on, so look for an underscore that was
02681               ** followed by a space, tab, comma, semicolon, colon, or period.
02682               */
02683 
02684               if (info->text_decor_uline_prev == '_')
02685                      switch (*uc) {
02686                      case ' ':
02687                      case '\t':
02688                      case ',':
02689                      case ';':
02690                      case ':':
02691                      case '.':
02692                             info->text_decor_state &= ~TEXT_DECOR_U;
02693                             /* Found it */
02694                             continue;
02695                      }
02696 
02697               /*
02698               ** If _ was suppressed, but, obviously, it's not followed by
02699               ** a space, emit the space in place of that _.
02700               */
02701 
02702               if (info->text_decor_uline_prev == '_')
02703                      text_process_plain(info, &space, 1);
02704 
02705               /*
02706               ** If the current character is _, suppress it.
02707               */
02708               if (*uc == '_')
02709               {
02710                      info->text_decor_uline_prev='_';
02711                      ++uc;
02712                      --cnt;
02713                      continue;
02714               }
02715 
02716               /* Otherwise look for the next _ character */
02717 
02718               for (i=0; i<cnt; ++i)
02719               {
02720                      if (uc[i] == '_')
02721                             break;
02722                      info->text_decor_uline_prev=uc[i];
02723               }
02724 
02725               if (i)
02726                      text_process_plain(info, uc, i);
02727 
02728               uc += i;
02729               cnt -= i;
02730        }
02731 }
02732 
02733 /***************************************************************************/
02734 
02735 /*
02736 ** End of the road. Only unmarked up, plain text left.
02737 */
02738 
02739 static void text_process_plain(struct msg2html_textplain_info *info,
02740                             const unicode_char *uc,
02741                             size_t cnt)
02742 {
02743        /* Set any requested text decorations that should be active now. */
02744        set_text_decor(info, info->text_decor_state);
02745 
02746        if (!info->ttline)
02747        {
02748               filter(&info->info, uc, cnt);
02749               return;
02750        }
02751 
02752        /*
02753        ** Within a <tt>, replace spaces by non-breakable spaces.
02754        */
02755 
02756        while (cnt)
02757        {
02758               size_t i;
02759 
02760               if (*uc == ' ')
02761               {
02762                      text_emit_passthru(info, "&nbsp;");
02763                      ++uc;
02764                      --cnt;
02765                      continue;
02766               }
02767 
02768               for (i=0; i<cnt; ++i)
02769               {
02770                      if (uc[i] == ' ')
02771                             break;
02772               }
02773 
02774               filter(&info->info, uc, i);
02775               uc += i;
02776               cnt -= i;
02777        }
02778 }
02779 
02780 struct msg2html_textplain_info *
02781 msg2html_textplain_start(const char *message_charset,
02782                       const char *output_character_set,
02783                       int isflowed,
02784                       int isdelsp,
02785                       int isdraft,
02786                       char *(*get_textlink)(const char *url, void *arg),
02787                       void *get_textlink_arg,
02788 
02789                       const char *smiley_index,
02790                       struct msg2html_smiley_list *smileys,
02791                       int wikifmt,
02792 
02793                       void (*output_func)(const char *p,
02794                                         size_t n, void *arg),
02795                       void *arg)
02796 {
02797        struct msg2html_textplain_info *tinfo=
02798               malloc(sizeof(struct msg2html_textplain_info));
02799                            
02800        memset(tinfo, 0, sizeof(*tinfo));
02801 
02802        tinfo->flowed=isflowed;
02803        tinfo->get_textlink=get_textlink;
02804        tinfo->get_textlink_arg=get_textlink_arg;
02805        tinfo->smiley_index=smiley_index;
02806        tinfo->smileys=smileys;
02807        tinfo->wikifmt=wikifmt;
02808 
02809        tinfo->text_url_handler=text_contents_notalpha;
02810        filter_start(&tinfo->info,
02811                    output_character_set,
02812                    output_func, arg);
02813 
02814        tinfo->conv_err=0;
02815        {
02816               struct rfc3676_parser_info pinfo;
02817 
02818               memset(&pinfo, 0, sizeof(pinfo));
02819 
02820               pinfo.charset=message_charset;
02821               pinfo.isflowed=isflowed;
02822               pinfo.isdelsp=isdelsp;
02823               pinfo.line_begin=text_line_begin;
02824               pinfo.line_contents=text_line_contents;
02825               pinfo.line_flowed_notify=text_line_flowed_notify;
02826               pinfo.line_end=text_line_end;
02827               pinfo.arg=tinfo;
02828 
02829               tinfo->parser=rfc3676parser_init(&pinfo);
02830        }
02831 
02832        if (tinfo->parser == NULL)
02833               tinfo->conv_err=1;
02834 
02835        if (!wikifmt)
02836        {
02837               text_emit_passthru(tinfo,
02838                                isflowed ?
02839                                "<div class=\"message-text-plain\">":
02840                                "<pre class=\"message-text-plain\">");
02841        }
02842 
02843        return tinfo;
02844 }
02845 
02846 void msg2html_textplain(struct msg2html_textplain_info *info,
02847                      const char *ptr,
02848                      size_t cnt)
02849 {
02850        if (info->parser)
02851               rfc3676parser(info->parser, ptr, cnt);
02852 }
02853 
02854 static int msg2html_textplain_trampoline(const char *ptr, size_t cnt, void *arg)
02855 {
02856        struct msg2html_textplain_info *info=
02857               (struct msg2html_textplain_info *)arg;
02858 
02859        msg2html_textplain(info, ptr, cnt);
02860        return 0;
02861 }
02862 
02863 int msg2html_textplain_end(struct msg2html_textplain_info *tinfo)
02864 {
02865        int errptr;
02866 
02867        if (tinfo->parser)
02868        {
02869               rfc3676parser_deinit(tinfo->parser, &errptr);
02870 
02871               if (errptr)
02872                      tinfo->conv_err=1;
02873        }
02874 
02875        text_set_quote_level(tinfo, 0);
02876        text_set_list_level(tinfo, "", 0);
02877        text_close_paragraph(tinfo);
02878 
02879        if (!tinfo->wikifmt)
02880        {
02881               text_emit_passthru(tinfo, tinfo->flowed ? "</div><br />\n":
02882                                "</pre><br />\n");
02883        }
02884 
02885        filter_end(&tinfo->info);
02886 
02887        if (tinfo->info.conversion_error)
02888               tinfo->conv_err=1;
02889 
02890        errptr=tinfo->conv_err;
02891 
02892        free(tinfo);
02893        return errptr;
02894 }
02895 
02896 static void output_html_func(const char *p, size_t n, void *dummy)
02897 {
02898         if (fwrite(p, 1, n, stdout) != n)
02899                 ; /* ignore */
02900 }
02901 
02902 static void showtextplain(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
02903                        struct msg2html_info *info)
02904 {
02905        int rc;
02906 
02907        const char *mime_charset, *dummy;
02908 
02909        int isflowed;
02910        int isdelsp;
02911 
02912        struct msg2html_textplain_info *tinfo;
02913        struct rfc2045src *src;
02914 
02915        rfc2045_mimeinfo(rfc, &dummy, &dummy, &mime_charset);
02916 
02917        isflowed=rfc2045_isflowed(rfc);
02918        isdelsp=0;
02919 
02920        if (isflowed)
02921               isdelsp=rfc2045_isdelsp(rfc);
02922 
02923        if (info->noflowedtext)
02924               isflowed=isdelsp=0;
02925 
02926        tinfo=msg2html_textplain_start(mime_charset,
02927                                    info->output_character_set,
02928                                    isflowed, isdelsp,
02929                                    info->is_preview_mode,
02930                                    info->get_textlink,
02931                                    info->arg,
02932                                    info->smiley_index,
02933                                    info->smileys,
02934                                    0,
02935                                    output_html_func, NULL);
02936 
02937        if (tinfo)
02938        {
02939               src=rfc2045src_init_fd(fileno(fp));
02940 
02941               if (src)
02942               {
02943                      rc=rfc2045_decodemimesection(src, rfc,
02944                                                &msg2html_textplain_trampoline,
02945                                                tinfo);
02946                      rfc2045src_deinit(src);
02947               }
02948        }
02949 
02950        rc=msg2html_textplain_end(tinfo);
02951 
02952        fseek(fp, 0L, SEEK_END);
02953        fseek(fp, 0L, SEEK_SET);    /* Resync stdio with uio */
02954 
02955        if (rc && info->charset_warning)
02956               (*info->charset_warning)(mime_charset, info->arg);
02957 
02958 }
02959 
02960 
02961 
02962 static void showkey(FILE *fp, struct rfc2045 *rfc, struct rfc2045id *id,
02963                   struct msg2html_info *info)
02964 {
02965        if (info->application_pgp_keys_action)
02966               (*info->application_pgp_keys_action)(id);
02967 }
02968 
02969 static void (*get_known_handler(struct rfc2045 *mime,
02970                             struct msg2html_info *info))
02971        (FILE *, struct rfc2045 *, struct rfc2045id *,
02972         struct msg2html_info *)
02973 {
02974 const char    *content_type, *dummy;
02975 
02976        rfc2045_mimeinfo(mime, &content_type, &dummy, &dummy);
02977        if (strncmp(content_type, "multipart/", 10) == 0)
02978               return ( &showmultipart );
02979 
02980        if (strcmp(content_type, "application/pgp-keys") == 0
02981            && info->gpgdir && libmail_gpg_has_gpg(info->gpgdir) == 0)
02982               return ( &showkey );
02983 
02984        if (mime->content_disposition
02985            && strcmp(mime->content_disposition, "attachment") == 0)
02986               return (0);
02987 
02988        if (strcmp(content_type, "text/plain") == 0 ||
02989            strcmp(content_type, "text/rfc822-headers") == 0 ||
02990            strcmp(content_type, "text/x-gpg-output") == 0)
02991               return ( &showtextplain );
02992        if (strcmp(content_type, "message/delivery-status") == 0)
02993               return ( &showdsn);
02994        if (info->showhtml && strcmp(content_type, "text/html") == 0)
02995               return ( &showtexthtml );
02996        if (strcmp(content_type, "message/rfc822") == 0)
02997               return ( &showmsgrfc822);
02998 
02999        return (0);
03000 }
03001 
03002 static void (*get_handler(struct rfc2045 *mime,
03003                        struct msg2html_info *info))
03004        (FILE *, struct rfc2045 *,
03005         struct rfc2045id *,
03006         struct msg2html_info *)
03007 {
03008        void (*func)(FILE *, struct rfc2045 *, struct rfc2045id *,
03009                    struct msg2html_info *);
03010 
03011        if ((func=get_known_handler(mime, info)) == 0)
03012               func= &showunknown;
03013 
03014        return (func);
03015 }
03016 
03017 static int download_func(const char *, size_t, void *);
03018 
03019 static void disposition_attachment(FILE *fp, const char *p, int attachment)
03020 {
03021        fprintf(fp, "Content-Disposition: %s; filename=\"", 
03022               attachment ? "attachment":"inline");
03023        while (*p)
03024        {
03025               if (*p == '"' || *p == '\\')
03026                      putc('\\', fp);
03027               if (!((unsigned char)(*p) < (unsigned char)' '))
03028                      putc(*p, fp);
03029               p++;
03030        }
03031        fprintf(fp, "\"\n");
03032 }
03033 
03034 
03035 void msg2html_download(FILE *fp, const char *mimeid, int dodownload,
03036                      const char *system_charset)
03037 {
03038        struct rfc2045 *rfc, *part;
03039        char   buf[BUFSIZ];
03040        int    n,cnt;
03041        const char    *content_type, *dummy, *charset;
03042        off_t  start_pos, end_pos, start_body;
03043        char   *content_name;
03044        off_t  ldummy;
03045 
03046        rfc=rfc2045_alloc();
03047 
03048        while ((n=fread(buf, 1, sizeof(buf), fp)) > 0)
03049               rfc2045_parse(rfc, buf, n);
03050        rfc2045_parse_partial(rfc);
03051 
03052        part=*mimeid ? rfc2045_find(rfc, mimeid):rfc;
03053        if (!part)
03054        {
03055               rfc2045_free(rfc);
03056               return;
03057        }
03058 
03059        rfc2045_mimeinfo(part, &content_type, &dummy, &charset);
03060 
03061        if (rfc2231_udecodeType(part, "name", system_charset,
03062                             &content_name) < 0)
03063               content_name=NULL;
03064 
03065        if (dodownload)
03066        {
03067               char *disposition_filename;
03068               const char *p;
03069 
03070               if (rfc2231_udecodeDisposition(part, "filename",
03071                                           (strncmp(content_type, "text/",
03072                                                  5) == 0 ?
03073                                           charset:system_charset),
03074                                           &disposition_filename) < 0)
03075               {
03076                      if (content_name)
03077                             free(content_name);
03078                      disposition_filename=NULL;
03079               }
03080 
03081 
03082               p=disposition_filename;
03083 
03084               if (!p || !*p) p=content_name;
03085               if (!p || !*p) p="message.dat";
03086               disposition_attachment(stdout, p, 1);
03087               content_type="application/octet-stream";
03088               if (disposition_filename)
03089                      free(disposition_filename);
03090        } else {
03091               if (content_name && *content_name)
03092                      disposition_attachment(stdout, content_name, 0);
03093        }
03094 
03095        printf(
03096               content_name && *content_name ?
03097               "Content-Type: %s; charset=%s; name=\"%s\"\n\n":
03098               "Content-Type: %s; charset=%s\n\n",
03099               content_type,
03100               charset,
03101               content_name ? content_name:"");
03102        if (content_name)
03103               free(content_name);
03104 
03105        rfc2045_mimepos(part, &start_pos, &end_pos, &start_body,
03106               &ldummy, &ldummy);
03107 
03108        if (*mimeid == 0)    /* Download entire message */
03109        {
03110               if (fseek(fp, start_pos, SEEK_SET) < 0)
03111               {
03112                      rfc2045_free(rfc);
03113                      return;
03114               }
03115 
03116               while (start_pos < end_pos)
03117               {
03118                      cnt=sizeof(buf);
03119                      if (cnt > end_pos-start_pos)
03120                             cnt=end_pos-start_pos;
03121                      cnt=fread(buf, 1, cnt, fp);
03122                      if (cnt <= 0) break;
03123                      start_pos += cnt;
03124                      download_func(buf, cnt, NULL);
03125               }
03126        }
03127        else
03128        {
03129               if (fseek(fp, start_body, SEEK_SET) < 0)
03130               {
03131                      rfc2045_free(rfc);
03132                      return;
03133               }
03134 
03135               rfc2045_cdecode_start(part, &download_func, 0);
03136 
03137               while (start_body < end_pos)
03138               {
03139                      cnt=sizeof(buf);
03140                      if (cnt > end_pos-start_body)
03141                             cnt=end_pos-start_body;
03142                      cnt=fread(buf, 1, cnt, fp);
03143                      if (cnt <= 0) break;
03144                      start_body += cnt;
03145                      rfc2045_cdecode(part, buf, cnt);
03146               }
03147               rfc2045_cdecode_end(part);
03148        }
03149        rfc2045_free(rfc);
03150 }
03151 
03152 static int download_func(const char *p, size_t cnt, void *voidptr)
03153 {
03154        if (fwrite(p, 1, cnt, stdout) != cnt)
03155               return (-1);
03156        return (0);
03157 }
03158 
03159 void msg2html_showmimeid(struct rfc2045id *idptr, const char *p)
03160 {
03161        if (!p)
03162               p="&amp;mimeid=";
03163 
03164        while (idptr)
03165        {
03166               printf("%s%d", p, idptr->idnum);
03167               idptr=idptr->next;
03168               p=".";
03169        }
03170 }