#include #include #include #include #include #include #include //#include #include #include #include "tlmpmail.h" #include "tlmpmail.m" #include "keys.h" PUBLIC BINBUF::BINBUF() { buf = NULL; len = -1; } PUBLIC BINBUF::BINBUF( const void *_buf, int _len) { len = 0; buf = NULL; setfrom (_buf,_len); } PUBLIC void BINBUF::free() { ::free (buf); buf = NULL; len = 0; } PUBLIC void BINBUF::setfrom ( const void *_buf, int _len) { free(); len = _len; if (len > 0){ buf = malloc(len+1); memcpy (buf,_buf,len); ((char*)buf)[len] = '\0'; // It may be used later as a string } } PUBLIC BINBUF::~BINBUF() { free (); } PUBLIC int BINBUF::size() { return len; } PUBLIC const void *BINBUF::get() { return buf; } /* Analyse a content_type field and extract the type and the boundary for mime parts. */ CONTENT_TYPE mime_parsetype (const char *content_type, SSTRING &boundary) { CONTENT_TYPE ret = CONTENT_TYPE_TEXT; boundary.setfrom (""); SSTRING_KEYS tb; mime_splitargs (content_type,tb); for (int i=0; iget(); if (stricmp(key,"MULTIPART/MIXED")==0){ ret = CONTENT_TYPE_MULTIPART_MIXED; }else if (stricmp(key,"MULTIPART/RELATED")==0){ ret = CONTENT_TYPE_MULTIPART_RELATED; }else if (stricmp(key,"MULTIPART/ALTERNATIVE")==0){ ret = CONTENT_TYPE_MULTIPART_ALTERNATIVE; }else if (stricmp(key,"application/octet-stream")==0){ ret = CONTENT_TYPE_DATA; }else if (stricmp(key,"BOUNDARY")==0){ boundary.setfrom (s->getobjval()); }else if (stricmp(key,"text/html")==0){ ret = CONTENT_TYPE_HTML; } } return ret; } PUBLIC ATTACH_INFO::ATTACH_INFO() { len = 0; body = NULL; is_binary = false; } static int char2int (char car) { int ret = -1; if (isdigit(car)){ ret = car - '0'; }else if (isxdigit(car)){ ret = toupper(car) - 'A' + 10; } return ret; } static int hextoi (const char *p) { return char2int(p[0])*16+char2int(p[1]); } static void mime_iconv ( const char *fromenc, // Source encoding const char *toenc, // Destination encoding char *&dst, int lendst, const char *src, int lensrc) { // const char *start_src = src; #if 0 fprintf (stderr,"fromenc = :%s: toenc = :%s:\n",fromenc,toenc); { FILE *fout = fopen ("/tmp/last","w"); fwrite (src,1,lensrc,fout); fclose (fout); } #endif if (fromenc == NULL || fromenc[0] == '\0'){ fprintf (stderr,MSG_R(E_NOICONV),fromenc,toenc); dst = stpcpy (dst,src); return; } iconv_t cd = iconv_open (toenc,fromenc); if (cd == iconv_t(-1)){ fprintf (stderr,MSG_U(E_NOICONV,"Can't convert from charset %s to %s") ,fromenc,toenc); fprintf (stderr,"\n"); dst = stpcpy (dst,src); }else{ bool is_utf8 = strcasecmp(fromenc,"utf-8")==0; char *in = (char*)src; size_t inlen = lensrc; size_t outlen = lendst; while (1){ size_t lenconv = iconv (cd,&in,&inlen,&dst,&outlen); if (lenconv != size_t(-1)){ break; }else if (is_utf8){ #if 0 unicode_char_t res; char *newin = unicode_get_utf8 (in,&res); if (newin == NULL){ tlmp_error (MSG_U(E_GETUTF8,"unicode_get_utf8 failed")); break; }else{ //fprintf (stderr,"skip %u bytes\n",(newin-in)); dst += snprintf (dst,10,"<%04x>",res); inlen -= (newin-in); in = newin; } #else if (outlen < 5) break; dst += snprintf (dst,10,"<%02x>",*in); in++; inlen--; outlen -= 4; #endif }else{ //fprintf (stderr,"error %d (%s,%s) %s\n%s\n",errno // ,fromenc,toenc,strerror(errno),start_src); break; } } iconv_close (cd); } *dst = '\0'; } /* Translate a quote printable string */ static int mime_translate_buf ( const char *s, SSTRING *sdst, BINBUF *bdst, char *tmp, int lentmp) { char *pt = tmp; enum { ENCODING_NONE, ENCODING_QUOTE, ENCODING_BASE64 }encode = ENCODING_NONE; int lens = strlen(s)+1; char fromenc[lens]; bool zero_seen = false; while (*s != '\0'){ // *pt = '\0'; // fprintf (stderr,"s=:%s: tmp=:%s:\n",s,tmp); if (*s == '='){ s++; if (s[0] == '?'){ // ISO encoding s++; char *ptenc = fromenc; while (*s != '\0' && *s != '?') *ptenc++ = *s++; *ptenc = '\0'; if (*s == '?'){ s++; char type = *s++; type = toupper(type); if (type == 'Q'){ encode = ENCODING_QUOTE; }else if (type == 'B'){ encode = ENCODING_BASE64; } if (*s == '?') s++; } }else if (*s == '\n'){ // Skip the line feed s++; }else{ int val = hextoi (s); if (val == 0) zero_seen = true; *pt++ = val; for (int i=0; i<2 && *s != '\0'; i++, s++); } }else if (s[0] == '?' && s[1] == '='){ // Do nothing ? s += 2; encode = ENCODING_NONE; }else if (encode == ENCODING_NONE){ *pt++ = *s++; }else if (encode == ENCODING_QUOTE){ if (*s == '_'){ *pt++ = ' '; s++; }else{ *pt++ = *s++; } }else if (encode == ENCODING_BASE64){ char dec[300]; int len = base64_decode (dec,300,s); if (len <= 0){ fprintf (stderr,"base64 %d :%s:\n",len,s); s++; }else{ while (*s != '\0' && *s != '?') s++; mime_iconv (fromenc,prefs_getcharset() ,pt,lentmp-(pt-tmp) ,dec,len); } } } *pt = '\0'; if (sdst != NULL){ if (zero_seen) tlmp_error ("Program error mime_translate_buf: text quote printable with zeros"); sdst->setfrom (tmp); } if (bdst != NULL){ bdst->setfrom (tmp,(int)(pt-tmp)); } return 0; } /* Translate a quote printable string */ static int mime_translate_gen (const char *s, SSTRING *sdst, BINBUF *bdst) { int ret = -1; int len = strlen(s)+1; // Avoid using too much stack and using malloc needlessly if (len < 1000){ char tmp[len]; ret = mime_translate_buf (s,sdst,bdst,tmp,len); }else{ char *tmp = (char*)malloc_err (len); ret = mime_translate_buf (s,sdst,bdst,tmp,len); free (tmp); } return ret; } int mime_translate (const char *s, SSTRING &dst) { return mime_translate_gen (s,&dst,NULL); } int mime_translate (const char *s, BINBUF &dst) { return mime_translate_gen (s,NULL,&dst); } int mime_translate (const SSTRING &s, SSTRING &dst) { return mime_translate (s.get(),dst); } int mime_translate (SSTRING &srcdst) { return mime_translate (srcdst,srcdst); } /* Parse a mime header lines with parameters */ int mime_splitargs (const char *str, SSTRING_KEYS &keys) { SSTRINGS tb; int ret = str_splitline (str,';',tb); for (int i=0; istrip_end(); const char *pt = s->get(); pt = str_skip(pt); const char *eq = strchr(pt,'='); int lentmp = strlen(pt)+1; char tmp[lentmp],tmpeq[lentmp]; strcpy (tmp,pt); if (eq != NULL){ tmp[(int)(eq-pt)] = '\0'; // Remove the quotes as needed eq++; if (eq[0] == '"'){ str_copyquote (tmpeq,eq); eq = tmpeq; } }else{ eq = ""; } strupr (tmp); keys.add (tmp,eq); } return ret; } /* Jump to the start of the next header entry. It skips continuation lines. */ static const char *mime_nextline (const char *body) { while (1){ while (*body != '\0' && *body != '\n') body++; if (*body == '\n') body++; if (*body == '\0' || *body == '\n' || *body > ' ') break; } return body; } /* Extract the fiels in a Content-type: line */ void mime_splittype (const char *type, SSTRING &content, SSTRING &charset) { SSTRING_KEYS tb; mime_splitargs (type,tb); for (int i=0; iicmp("text/plain")==0){ content = "text/plain"; }else if (s->icmp("text/html")==0){ content = "text/html"; }else if (s->icmp("charset")==0){ charset = s->getobjval(); } } } static void mime_callpart ( _F_mime_split &c, int no, const char *type, const char *encoding, const char *description, const char *disposition, const char *defcharset, const char *start, const char *end, bool decode) { // Ok, we have seen a part and know where it ends int lenpart = (int)(end-start); char *tmp = (char*)malloc_err(lenpart+1); char *binbuf = NULL; memcpy (tmp,start,lenpart); tmp[lenpart] = '\0'; // fprintf (stderr,"part :%s: :%s: :%s: :%s:\n",type,encoding,description,disposition); ATTACH_INFO info; BINBUF bodydecode; info.encoding.setfrom (encoding); info.is_binary = false; if (decode && stricmp(encoding,"QUOTED-PRINTABLE")==0){ mime_translate (tmp,bodydecode); info.body = (const char*)bodydecode.get(); info.len = bodydecode.size(); }else if (decode && stricmp(encoding,"base64")==0){ binbuf = (char*)malloc_err(lenpart+1); // The function decode a single line at a time const char *src = tmp; info.len = 0; const char *lastsrc = NULL; while (*src != '\0' && src != lastsrc){ lastsrc = src; info.len += base64_decode(binbuf+info.len, 100, src); while (*src > ' ') src++; if (*src == '\n') src++; } info.is_binary = true; info.body = binbuf; }else{ info.body = tmp; info.len = strlen(tmp); } SSTRING_KEYS tb; mime_splitargs (type,tb); mime_splitargs (disposition,tb); #if 0 fprintf (stderr,"type=:%s:\n",type); fprintf (stderr,"disp=:%s:\n",disposition); for (int i=0; iget(),s->getobjval()); } #endif bool text_plain = true; // We use the name or filename tag as the description by default SSTRING charset (defcharset); for (int i=0; iicmp("name")==0){ mime_translate (s->getobjval(),info.name); }else if (info.description.is_empty() && s->icmp("filename")==0){ mime_translate (s->getobjval(),info.name); }else if (s->icmp("text/plain")==0){ text_plain = true; }else if (s->icmp("charset")==0){ charset = s->getobjval(); } } if (description[0] != '\0'){ // We override the definition if supplied mime_translate (description,info.description); } if (info.description.is_empty()){ mime_translate (info.name,info.description); } char *newbody = NULL; if (no == 0 && text_plain){ if (charset.is_filled()){ int newlenbody = 2*info.len+1; newbody = (char*)malloc(newlenbody); char *endnewbody = newbody; mime_iconv (charset.get(),prefs_getcharset() ,endnewbody,newlenbody ,info.body,info.len); info.body = newbody; info.len = strlen(newbody); } SSTRING buf; miscmail_formattext (info.body,buf); info.body = buf.get(); c.mainmsg (no,info); }else{ c.part (no,info); } free (tmp); free (binbuf); free (newbody); } void _F_mime_split::mainmsg(int no, ATTACH_INFO &info) { // If the user has not defined the mainmsg functag, he will // receive the mainmsg as any other attachement. part (no,info); } void _F_mime_split::part(int, ATTACH_INFO &) { } static const char *mime_subsplit ( _F_mime_split &c, const char *body, const char *boundary, bool decode, int &partnum); static void mime_dispatch ( _F_mime_split &c, const char *start, const char *end, const SSTRING &type, const SSTRING &encoding, const SSTRING &description, const SSTRING &disposition, bool decode, int &partnum) { #if 0 fprintf (stderr,"dispatch %d :%s: :%s: :%s: :%s:\n",(int)(end-start) ,type.get(),encoding.get(),description.get(),disposition.get()); #endif // Ok, we have seen a part and know where it ends SSTRING newbound; CONTENT_TYPE ctype = mime_parsetype (type.get(),newbound); if (ctype == CONTENT_TYPE_TEXT || ctype == CONTENT_TYPE_HTML || ctype == CONTENT_TYPE_DATA){ mime_callpart (c,partnum,type.get(),encoding.get() ,description.get(),disposition.get(),"" ,start,end,decode); partnum++; }else if (ctype == CONTENT_TYPE_MULTIPART_ALTERNATIVE){ mime_subsplit (c,start,newbound.get(),decode,partnum); }else if (ctype == CONTENT_TYPE_MULTIPART_MIXED){ mime_subsplit (c,start,newbound.get(),decode,partnum); }else if (ctype == CONTENT_TYPE_MULTIPART_RELATED){ mime_subsplit (c,start,newbound.get(),decode,partnum); }else{ fprintf (stderr,"Don't know how to handle sub type %s\n",type.get()); } } static const char *mime_subsplit ( _F_mime_split &c, const char *body, const char *boundary, bool decode, int &partnum) { SSTRING tmp,tmp1; tmp.setfromf ("--%s\n",boundary); tmp1.setfromf ("--%s--\n",boundary); int len = tmp.getlen(); boundary = tmp.get(); int len_end = tmp1.getlen(); const char *boundary_end = tmp1.get(); const char *start = NULL; // Start of a part SSTRING encoding,type,description,disposition; while (*body != '\0'){ if (strncmp(body,boundary,len)==0){ if (start != NULL){ mime_dispatch (c,start,body,type,encoding,description ,disposition,decode,partnum); } encoding.setempty(); description.setempty(); disposition.setempty(); type.setempty(); body += len; // Parse the header. End with empty line while (*body != '\0' && *body != '\n'){ const char *end = mime_nextline(body); int lenline = (int)(end-body); char data[lenline+1]; memcpy (data,body,lenline); data[lenline] = '\0'; strip_end (data); if (strncasecmp(body,"Content-Type:",13)==0){ type.setfrom (str_skip(data+13)); }else if (strncasecmp(body,"Content-Transfer-Encoding:",26)==0){ encoding.setfrom (str_skip(data+26)); }else if (strncasecmp(body,"Content-Description:",20)==0){ description.setfrom (str_skip(data+20)); }else if (strncasecmp(body,"Content-Disposition:",20)==0){ disposition.setfrom (str_skip(data+20)); } body = end; } if (*body == '\n') body++; start = body; }else if (strncmp(body,boundary_end,len_end)==0){ if (start != NULL){ mime_dispatch (c,start,body,type,encoding,description ,disposition,decode,partnum); start = NULL; } break; }else{ body = mime_nextline (body); } } if (start != NULL){ mime_dispatch (c,start,body,type,encoding,description ,disposition,decode,partnum); } return body; } /* Decompose a mail body in MIME component */ int mime_split ( _F_mime_split &c, const char *body, const char *boundary, const char *encoding, const char *charset, CONTENT_TYPE type, const char *description, bool decode, // Decode quote printable text bool formatbody) // Pretty format the body as needed { int ret = 0; if (boundary == NULL || boundary[0] == '\0'){ if (type == CONTENT_TYPE_DATA){ // The message is an attachement only. ATTACH_INFO info; info.body = ""; c.mainmsg (0,info); SSTRING tmpdisp; tmpdisp.setfromf ("filename=\"%s\"",description); mime_callpart (c,1,"",encoding,description,tmpdisp.get(),charset ,body,body+strlen(body),decode); }else{ // Not a multi-part, so we send everything as a main message SSTRING decostr; if (decode && strcasecmp(encoding,"quoted-printable")==0){ mime_translate (body,decostr); body = decostr.get(); } SSTRING tmp; ATTACH_INFO info; if (type == CONTENT_TYPE_HTML){ miscmail_cnvhtml (body,tmp); info.body = tmp.get(); }else if (formatbody){ miscmail_formattext (body,tmp); info.body = tmp.get(); }else{ info.body = body; } const char *end = info.body + strlen(info.body); mime_callpart (c,0,"text/plain",encoding,"","",charset ,info.body,end,decode); ret = 1; } }else{ mime_subsplit (c,body,boundary,decode,ret); } return ret; } int mime_split ( _F_mime_split &c, const MAIL_MESSAGE_FULL &full, bool decode, // Decode quote printable text bool formatbody) // Pretty format the body as needed { SSTRING boundary; full.parsetype(boundary); return mime_split (c,full.text.get(),boundary.get() ,full.content_encoding.get(),full.charset.get() ,full.gettype(),full.content_description.get() ,decode,formatbody); } int mime_getmainmsg(const MAIL_MESSAGE_FULL &full, SSTRING &mainmsg) { SSTRING *mainmsg; glocal.mainmsg = &mainmsg; (full,true,true); glocal.mainmsg->setfrom (info.body); return 0; } static SSTRING curdir; /* Process one attachement */ void mime_doattach (MAIL_MESSAGE_FULL &full, bool _tbsel[], int nbsel) { glocal DIALOG dia; glocal bool *tbsel = _tbsel; glocal SSTRINGS fnames; glocal.dia.settype (DIATYPE_POPUP); (full,false,false); SSTRING *fname = new SSTRING(info.name); glocal.fnames.add(fname); if (glocal.tbsel[no]){ glocal.dia.gui_group(""); glocal.dia.newf_info (MSG_R(F_DESCRIPTION),info.description.get()); glocal.dia.newline(); char tmp[20]; sprintf (tmp,"%d",info.len); glocal.dia.newf_info (MSG_U(F_SIZE,"Size"),tmp); glocal.dia.newline(); glocal.dia.newf_str (MSG_R(F_FILENAME),*fname,60); glocal.dia.newline(); glocal.dia.gui_end(); glocal.dia.gui_dispolast (GUI_H_LEFT,2,GUI_V_CENTER,1); glocal.dia.newline(); } curdir = linuxconf_getval (K_ATTACH,K_CURDIR,""); glocal.dia.newf_str (MSG_U(F_CURDIR,"Working directory"),curdir,60); glocal.dia.newline(); SSTRING command; FIELD_COMBO *combo = glocal.dia.newf_combo (MSG_U(F_COMMAND,"Pipe to command") ,command); SSTRINGS tb; int n = linuxconf_getall (K_PIPECOMMAND,K_INDEX,tb,true); for (int i=0; iaddopt (tb.getitem(i)->get()); } glocal.dia.newline(); glocal.dia.setbutinfo (MENU_USR1,MSG_U(B_VIEW,"View"),MSG_R(B_VIEW)); glocal.dia.setbutinfo (MENU_USR2,MSG_U(B_SAVE,"Save"),MSG_R(B_SAVE)); glocal.dia.setbutinfo (MENU_USR3,MSG_U(B_PIPE,"Pipe"),MSG_R(B_PIPE)); int nof = 0; while (1){ MENU_STATUS code = glocal.dia.edit (MSG_U(T_ATTACHEMENT,"Attachement") ,"",help_nil,nof ,MENUBUT_CANCEL|MENUBUT_USR1|MENUBUT_USR2|MENUBUT_USR3); if (code == MENU_CANCEL || code == MENU_ESCAPE){ break; }else if (code == MENU_USR1){ }else if (code == MENU_USR2){ glocal.dia.save(); bool one_empty = false; for (int i=0; iis_empty()) one_empty = true; } if (one_empty){ xconf_error (MSG_U(E_NOFILENAME,"Specify a file name")); }else{ glocal int ok = 0; (full,true,true); if (glocal.tbsel[no]){ bool mode = false; const char *file = glocal.fnames.getitem(no)->get(); SSTRING path; if (file[0] != '/' && curdir.is_filled()){ path.setfromf ("%s/%s",curdir.get(),file); file = path.get(); } if (miscmail_filemode (file,mode)){ glocal ATTACH_INFO *info = &info; glocal.ok = (file,mode); fwrite (glocal.info->body,1,glocal.info->len,fout); return 0; } } if (glocal.ok != -1) break; } }else if (code == MENU_USR3){ glocal.dia.save(); if (tb.lookup(command.get())==-1){ tb.add (new SSTRING(command)); linuxconf_replace (K_PIPECOMMAND,K_INDEX,tb); linuxconf_save(); } POPENUSER pop (command.get()); if (pop.isok()){ glocal FILE *fout = pop.getfout(); (full,true,true); if (glocal.tbsel[no]) fwrite (info.body,1,info.len,glocal.fout); pop.close(); SSTRING out; while (pop.wait(10)>=0){ char line[1000]; while (pop.readout (line,sizeof(line)-1)!=-1){ out.appendf (" %s",line); } while (pop.readerr (line,sizeof(line)-1)!=-1){ out.appendf ("* %s",line); } } xconf_notice (MSG_U(N_PIPEREPORTED ,"Pipe command reported:\n\n%s"),out.get()); break; } } } linuxconf_replace (K_ATTACH,K_CURDIR,curdir); linuxconf_save(); } void mime_doattach (MAIL_MESSAGE_FULL &msg) { bool tbsel[]={false,true}; mime_doattach (msg,tbsel,1); }