1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * Pan - A Newsreader for Gtk+
4  * Copyright (C) 2002-2006  Charles Kerr <charles@rebelbase.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <config.h>
21 #include <cctype>
22 #include <cstring>
23 #include <cstdlib>
24 #include <iostream>
25 #include <sstream>
26 extern "C"
27 {
28   #include <unistd.h>
29   #include <gmime/gmime.h>
30 }
31 #include <pan/general/debug.h>
32 #include <pan/general/macros.h>
33 #include <pan/general/messages.h>
34 #include <pan/general/string-view.h>
35 #include <pan/general/log.h>
36 #include "mime-utils.h"
37 #include "gpg.h"
38 
39 #define is_nonempty_string(a) ((a) && (*a))
40 
41 using namespace pan;
42 
43 namespace pan
44 {
45 
46   iconv_t conv(0);
47   bool iconv_inited(false);
48 
49   char *
__g_mime_iconv_strndup(iconv_t cd,const char * str,size_t n,const char * charset)50   __g_mime_iconv_strndup (iconv_t cd, const char *str, size_t n, const char* charset)
51   {
52     iconv_t ic(0), backup(0);
53     if (charset)
54     {
55       ic = iconv_open ("UTF-8", charset);
56       backup = conv;
57       conv = ic;
58     }
59 
60     size_t inleft, outleft, converted = 0;
61     char *out, *outbuf;
62     const char *inbuf;
63     size_t outlen;
64     int errnosav;
65 
66     if (cd == (iconv_t) -1 || !iconv_inited)
67       return g_strndup (str, n);
68 
69     outlen = n * 2 + 16;
70     out = (char*)g_malloc (outlen + 4);
71 
72     inbuf = str;
73     inleft = n;
74 
75     do {
76       errno = 0;
77       outbuf = out + converted;
78       outleft = outlen - converted;
79 
80 #if defined(__NetBSD__)
81       converted = iconv (cd, &inbuf, &inleft, &outbuf, &outleft);
82 #else
83       converted = iconv (cd, (char **) &inbuf, &inleft, &outbuf, &outleft);
84 #endif
85 
86       if (converted != (size_t) -1 && errno == 0) {
87         /*
88          * EINVAL  An  incomplete  multibyte sequence has been encoun�
89          *         tered in the input.
90          *
91          * We'll just have to ignore it...
92          */
93         break;
94       }
95 
96       if (errno != E2BIG && errno != EILSEQ) {
97         errnosav = errno;
98         g_free (out);
99 
100         /* reset the cd */
101         iconv (cd, NULL, NULL, NULL, NULL);
102 
103         errno = errnosav;
104 
105         return NULL;
106       }
107 
108       /*
109 		 * E2BIG   There is not sufficient room at *outbuf.
110 		 *
111 		 * We just need to grow our outbuffer and try again.
112 		 */
113       {
114         converted = outbuf - out;
115         outlen += inleft * 2 + 16;
116         out = (char*)g_realloc (out, outlen + 4);
117         outbuf = out + converted;
118       }
119 
120     } while (TRUE);
121 
122     /* flush the iconv conversion */
123     while (iconv (cd, NULL, NULL, &outbuf, &outleft) == (size_t) -1) {
124       if (errno != E2BIG)
125         break;
126 
127       outlen += 16;
128       converted = outbuf - out;
129       out = (char*)g_realloc (out, outlen + 4);
130       outleft = outlen - converted;
131       outbuf = out + converted;
132     }
133 
134     /* Note: not all charsets can be nul-terminated with a single
135              nul byte. UCS2, for example, needs 2 nul bytes and UCS4
136              needs 4. I hope that 4 nul bytes is enough to terminate all
137              multibyte charsets? */
138 
139     /* nul-terminate the string */
140     memset (outbuf, 0, 4);
141 
142     /* reset the cd */
143     iconv (cd, NULL, NULL, NULL, NULL);
144 
145     if (backup) conv = backup;
146 
147     return out;
148   }
149 }
150 
151 namespace
152 {
153    const char*
__yenc_extract_tag_val_char(const char * line,const char * tag)154    __yenc_extract_tag_val_char (const char * line, const char *tag)
155    {
156       const char * retval = NULL;
157 
158       const char * tmp = strstr (line, tag);
159       if (tmp != NULL) {
160          tmp += strlen (tag);
161          if (*tmp != '\0')
162             retval = tmp;
163       }
164 
165       return retval;
166    }
167 
168    guint
__yenc_extract_tag_val_int_base(const char * line,const char * tag,int base)169    __yenc_extract_tag_val_int_base (const char * line,
170                                     const char * tag,
171                                     int          base)
172    {
173       guint retval = 0;
174 
175       const char * tmp = __yenc_extract_tag_val_char (line, tag);
176       if (tmp != NULL) {
177          char * tail = NULL;
178          retval = strtoul (tmp, &tail, base);
179          if (tmp == tail)
180             retval = 0;
181       }
182 
183       return retval;
184    }
185 
186    int
__yenc_extract_tag_val_hex_int(const char * line,const char * tag)187    __yenc_extract_tag_val_hex_int (const char * line,
188                                    const char * tag)
189    {
190       return __yenc_extract_tag_val_int_base (line, tag, 16);
191    }
192 
193    int
__yenc_extract_tag_val_int(const char * line,const char * tag)194    __yenc_extract_tag_val_int (const char *line,
195                                const char *tag)
196    {
197       return __yenc_extract_tag_val_int_base (line, tag, 0);
198    }
199 
200    int
yenc_parse_end_line(const char * b,size_t * size,int * part,guint * pcrc,guint * crc)201    yenc_parse_end_line (const char * b,
202                         size_t     * size,
203                         int        * part,
204                         guint      * pcrc,
205                         guint      * crc)
206    {
207       // find size
208       const char * pch = __yenc_extract_tag_val_char (b, YENC_TAG_SIZE);
209       size_t _size (0);
210       if (pch)
211          _size = strtoul(pch, NULL, 10);
212       pan_return_val_if_fail (_size!=0, -1);
213 
214       // part is optional
215       int _part = __yenc_extract_tag_val_int (b, YENC_TAG_PART);
216       guint _pcrc = __yenc_extract_tag_val_hex_int (b, YENC_TAG_PCRC32);
217       if (part != 0)
218          pan_return_val_if_fail( _pcrc != 0, -1 );
219 
220       guint _crc = __yenc_extract_tag_val_hex_int( b, YENC_TAG_CRC32);
221 
222       if (size)
223          *size = _size;
224       if (part)
225          *part = _part;
226       if (pcrc)
227          *pcrc = _pcrc;
228       if (crc)
229          *crc = _crc;
230 
231       return 0;
232    }
233 
234    /**
235     * yenc_parse_being_line
236     * @param line the line to check for "begin [part] line size filename"
237     * @param filename if parse is successful, is set with the
238     *        starting character of the filename.
239     * @param line_len if parse is successful, is set with the line length
240     * @param part if parse is successful this is set to the current attachement's
241     *       part number
242     * @return 0 on success, -1 on failure
243     */
244    int
yenc_parse_begin_line(const char * b,char ** file,int * line_len,int * attach_size,int * part)245    yenc_parse_begin_line (const char  * b,
246                           char       ** file,
247                           int         * line_len,
248                           int         * attach_size,
249                           int         * part)
250    {
251       int ll = __yenc_extract_tag_val_int (b, YENC_TAG_LINE);
252       pan_return_val_if_fail (ll != 0, -1);
253 
254       // part is optional
255       int part_num = __yenc_extract_tag_val_int (b, YENC_TAG_PART);
256 
257       int a_sz = __yenc_extract_tag_val_int( b, YENC_TAG_SIZE );
258       pan_return_val_if_fail( a_sz != 0, -1 );
259 
260       const char * fname = __yenc_extract_tag_val_char (b, YENC_TAG_NAME);
261       pan_return_val_if_fail( fname, -1 );
262 
263       if (line_len)
264          *line_len = ll;
265       if (file) {
266          const char * pch = strchr (fname, '\n');
267          *file = g_strstrip (g_strndup (fname, pch-fname));
268       }
269       if (part)
270          *part = part_num;
271       if (attach_size)
272          *attach_size = a_sz;
273 
274       return 0;
275    }
276 
277    /*
278     * a =ypart line requires both begin & end offsets. These are the location
279     * of the part inside the end file
280     */
281    int
yenc_parse_part_line(const char * b,guint * begin_offset,guint * end_offset)282    yenc_parse_part_line (const char * b, guint *begin_offset, guint *end_offset)
283    {
284       int bg = __yenc_extract_tag_val_int( b, YENC_TAG_BEGIN );
285       if (bg == 0)
286          return -1;
287 
288       int end = __yenc_extract_tag_val_int( b, YENC_TAG_END );
289       if (end == 0)
290          return -1;
291 
292       if (begin_offset)
293          *begin_offset = bg;
294       if (end_offset)
295          *end_offset = end;
296 
297       return 0;
298    }
299 
300    /**
301     * yenc_is_beginning
302     * @param line line to test & see if it's the beginning of a yenc block
303     * @return true if it is, false otherwise
304     */
305    bool
yenc_is_beginning_line(const char * line)306    yenc_is_beginning_line (const char * line)
307    {
308       return !strncmp (line, YENC_MARKER_BEGIN, YENC_MARKER_BEGIN_LEN) &&
309              !yenc_parse_begin_line (line, NULL, NULL, NULL, NULL);
310    }
311 
312    bool
yenc_is_part_line(const char * line)313    yenc_is_part_line (const char * line)
314    {
315       return !strncmp (line, YENC_MARKER_PART, YENC_MARKER_PART_LEN) &&
316              !yenc_parse_part_line (line, NULL, NULL);
317    }
318 
319    /**
320     * yenc_is_ending_line
321     * @param line line to test & see if it's the end of a yenc block
322     * @return true if it is, false otherwise
323     */
324    bool
yenc_is_ending_line(const char * line)325    yenc_is_ending_line (const char * line)
326    {
327       return !strncmp (line, YENC_MARKER_END, YENC_MARKER_END_LEN) &&
328              !yenc_parse_end_line (line, NULL, NULL, NULL, NULL);
329    }
330 
331 };
332 
333 
334 /***
335 **** UU
336 ***/
337 
338 namespace
339 {
340    int
uu_parse_begin_line(const StringView & begin,char ** setme_filename,gulong * setme_mode)341    uu_parse_begin_line (const StringView & begin,
342                         char            ** setme_filename,
343                         gulong           * setme_mode)
344    {
345       int retval;
346       pan_return_val_if_fail (!begin.empty(), -1);
347 
348       // skip past the "begin "
349       StringView tmp = begin.substr (begin.str+6, NULL);
350       char * end;
351       gulong mode = strtoul (tmp.str, &end, 8);
352       if (end==NULL || end-tmp.str<3) /* must have at least 3 octal digits */
353          mode = 0ul;
354 
355       tmp = tmp.substr (end, NULL); // skip past the permissions
356       tmp = tmp.substr (NULL, tmp.strchr('\n')); // remove linefeed, if any
357       tmp.trim ();
358 
359       if (mode==0 || tmp.empty())
360          retval = -1;
361       else {
362          if (setme_mode != NULL)
363             *setme_mode = mode;
364          if (setme_filename != NULL)
365             *setme_filename = g_strndup (tmp.str, tmp.len);
366          retval = 0;
367       }
368 
369       return retval;
370    }
371 
372    /**
373     * uu_is_beginning
374     * @param line line to test & see if it's the beginning of a uu-encoded block
375     * @return true if it is, false otherwise
376     */
377    bool
uu_is_beginning_line(const StringView & line)378    uu_is_beginning_line (const StringView& line)
379    {
380       return !line.empty()
381          && line.len>5
382          && (!memcmp(line.str, "begin ", 6) || !memcmp(line.str, "BEGIN ", 6))
383          && !uu_parse_begin_line (line, NULL, NULL);
384    }
385 
386    /**
387     * uu_is_ending
388     * @param line line to test & see if it's the end of a uu-encoded block
389     * @return true if it is, false otherwise
390     */
391    bool
uu_is_ending_line(const char * line)392    uu_is_ending_line (const char * line)
393    {
394       return line!=0
395           && (line[0]=='e' || line[0]=='E')
396           && (line[1]=='n' || line[1]=='N')
397           && (line[2]=='d' || line[2]=='D')
398           && !strstr(line,"cut") && !strstr(line,"CUT");
399    }
400 
401    bool
is_uu_line(const char * line,int len)402    is_uu_line (const char * line, int len)
403    {
404       pan_return_val_if_fail (line!=NULL, FALSE);
405 
406       if (*line=='\0' || len<1)
407          return false;
408 
409       if (len==1 && *line=='`')
410          return true;
411 
412       // get octet length
413       const int octet_len = *line - 0x20;
414       if (octet_len > 45)
415          return false;
416 
417       // get character length
418       int char_len = (octet_len / 3) * 4;
419       switch (octet_len % 3) {
420          case 0: break;
421          case 1: char_len += 2; break;
422          case 2: char_len += 3; break;
423       }
424       if (char_len+1 > len)
425          return false;
426 
427       /* if there's a lot of noise at the end, be suspicious.
428          This is to keep out lines of nntp-server-generated
429          taglines that have a space as the first character */
430       if (char_len+10 < len)
431          return false;
432 
433       // make sure each character is in the uuencoded range
434       for (const char *pch=line+1, *line_end=pch+char_len; pch!=line_end; ++pch)
435          if (*pch<0x20 || *pch>0x60)
436             return false;
437 
438       // looks okay
439       return true;
440    }
441 }
442 
443 namespace
444 {
445   guint
stream_readln(GMimeStream * stream,GByteArray * line,gint64 * startpos)446   stream_readln (GMimeStream *stream, GByteArray *line, gint64* startpos)
447   {
448     char linebuf[1024];
449     gssize len;
450 
451     /* sanity clause */
452     pan_return_val_if_fail (stream!=NULL, 0u);
453     pan_return_val_if_fail (line!=NULL, 0u);
454     pan_return_val_if_fail (startpos!=NULL, 0u);
455 
456     /* where are we now? */
457     *startpos = g_mime_stream_tell (stream);
458 
459     /* fill the line array */
460     g_byte_array_set_size (line, 0);
461     if (!g_mime_stream_eos (stream)) do {
462       len = g_mime_stream_buffer_gets (stream, linebuf, sizeof(linebuf));
463       if (len>0)
464         g_byte_array_append (line, (const guint8 *)linebuf, len);
465     } while (len>0 && linebuf[len-1]!='\n');
466 
467     return line->len;
468   }
469 }
470 
471 enum EncType
472 {
473 	ENC_PLAIN ,
474 	ENC_YENC,
475 	ENC_UU
476 };
477 
478 namespace pan
479 {
480   struct TempPart
481   {
482     GMimeStream * stream;
483     GMimeFilter * filter;
484     GMimeStream * filter_stream;
485     char * filename;
486     unsigned int valid_lines;
487     EncType type;
488 
489     int y_line_len;
490     int y_attach_size;
491     int y_part;
492     guint y_offset_begin;
493     guint y_offset_end;
494     guint y_crc;
495     guint y_pcrc;
496     size_t y_size;
497 
TempPartpan::TempPart498     TempPart (EncType intype=ENC_UU, char *infilename=0): stream(0), filter(0),
499       filter_stream(0), filename(infilename), valid_lines(0), type(intype),
500       y_line_len(0), y_attach_size(0), y_part(0),
501       y_offset_begin(0), y_offset_end(0),
502       y_crc(0), y_pcrc(0), y_size(0)
503       {}
504 
~TempPartpan::TempPart505     ~TempPart () {
506       g_free (filename);
507       g_object_unref (stream);
508       if (filter)
509         g_object_unref (filter);
510       if (filter_stream)
511         g_object_unref (filter_stream);
512     }
513   };
514 
515   typedef std::vector<TempPart*> temp_parts_t;
516 
find_filename_part(temp_parts_t & parts,const char * filename)517   TempPart* find_filename_part (temp_parts_t& parts, const char * filename)
518   {
519     if (filename && *filename) {
520       foreach (temp_parts_t, parts, it) {
521         TempPart * part (*it);
522         if (part->filename && !strcmp(filename,part->filename))
523           return part;
524       }
525     }
526     return 0;
527   }
528 
append_if_not_present(temp_parts_t & parts,TempPart * part)529   bool append_if_not_present (temp_parts_t& parts, TempPart * part)
530   {
531     foreach (temp_parts_t, parts, it)
532       if (part == *it)
533         return false;
534     parts.push_back (part);
535     return true;
536   }
537 
538 
apply_source_and_maybe_filter(TempPart * part,GMimeStream * s)539   void apply_source_and_maybe_filter (TempPart * part, GMimeStream * s)
540   {
541 
542     if (!part->stream) {
543       part->stream = g_mime_stream_mem_new ();
544       if (part->type != ENC_PLAIN) {
545         part->filter_stream = g_mime_stream_filter_new (part->stream);
546         switch (part->type)
547         {
548           case ENC_UU:
549             part->filter = g_mime_filter_basic_new (GMIME_CONTENT_ENCODING_UUENCODE, FALSE);
550             break;
551 
552 //          case ENC_BASE64:
553 //            part->filter = g_mime_filter_basic_new (GMIME_CONTENT_ENCODING_BASE64, FALSE);
554 //            break;
555 //
556 //          case ENC_QP:
557 //            part->filter = g_mime_filter_basic_new (GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE, FALSE);
558 //            break;
559 
560           case ENC_YENC:
561             part->filter = g_mime_filter_yenc_new (FALSE);
562             break;
563         }
564 
565         g_mime_stream_filter_add (GMIME_STREAM_FILTER(part->filter_stream),
566                                   part->filter);
567       }
568     }
569 
570     g_mime_stream_write_to_stream (s, (part->type == ENC_PLAIN  ? part->stream : part->filter_stream));
571 
572     g_object_unref (s);
573   }
574 
575   struct sep_state
576   {
577     temp_parts_t master_list;
578     temp_parts_t current_list;
579     TempPart *uu_temp;
sep_statepan::sep_state580     sep_state():uu_temp(NULL) {};
581   };
582 
583   bool
separate_encoded_parts(GMimeStream * istream,sep_state & state,EncType et)584   separate_encoded_parts (GMimeStream  * istream, sep_state &state, EncType et)
585   {
586     temp_parts_t& master(state.master_list);
587     temp_parts_t& appendme(state.current_list);
588     TempPart * cur = NULL;
589     EncType enc_type = et;
590     GByteArray * line;
591     gboolean yenc_looking_for_part_line = FALSE;
592     gint64 linestart_pos = 0;
593     gint64 sub_begin = 0;
594     guint line_len;
595     bool found = false;
596 
597     /* sanity clause */
598     pan_return_val_if_fail (istream!=NULL,false);
599 
600     sub_begin = 0;
601     line = g_byte_array_sized_new (4096);
602 
603     while ((line_len = stream_readln (istream, line, &linestart_pos)))
604     {
605       char * line_str = (char*) line->data;
606       char * pch = strchr (line_str, '\n');
607       if (pch != NULL) {
608         pch[1] = '\0';
609         line_len = pch - line_str;
610       }
611 
612       switch (enc_type)
613       {
614 
615 //        case ENC_QP:
616 //          sub_begin = linestart_pos;
617 //          cur = new TempPart(type = ENC_QP);
618 //          break;
619 //
620 //        case ENC_BASE64:
621 //          sub_begin = linestart_pos;
622 //          cur = new TempPart(type = ENC_BASE64);
623 //          break;
624 
625         case ENC_PLAIN:
626         {
627           const StringView line_pstr (line_str, line_len);
628 
629           if (uu_is_beginning_line (line_pstr))
630           {
631             gulong mode = 0ul;
632             char * filename = NULL;
633 
634             found=true;
635             // flush the current entry
636             if (cur != NULL) {
637               GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
638               apply_source_and_maybe_filter (cur, s);
639 
640               if ( append_if_not_present (master, cur) )
641                 append_if_not_present (appendme, cur);
642               cur = NULL;
643             }
644 
645             // start a new section (or, if filename matches, continue an existing one)
646             sub_begin = linestart_pos;
647             uu_parse_begin_line (line_pstr, &filename, &mode);
648             cur = find_filename_part (master, filename);
649             if (cur)
650               g_free (filename);
651             else
652               cur = new TempPart (enc_type=ENC_UU, filename);
653             state.uu_temp = cur;
654           }
655           else if (yenc_is_beginning_line (line_str))
656           {
657             found = true;
658             // flush the current entry
659             if (cur != NULL) {
660               GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
661               apply_source_and_maybe_filter (cur, s);
662 
663               if ( append_if_not_present (master, cur) )
664                 append_if_not_present (appendme, cur);
665               cur = NULL;
666             }
667             sub_begin = linestart_pos;
668 
669             // start a new section (or, if filename matches, continue an existing one)
670             char * fname;
671             int line_len, attach_size, part;
672             yenc_parse_begin_line (line_str, &fname, &line_len, &attach_size, &part);
673             cur = find_filename_part (master, fname);
674             if (cur) {
675               g_free (fname);
676               g_mime_filter_yenc_set_state (GMIME_FILTER_YENC (cur->filter),
677                                             GMIME_YDECODE_STATE_INIT);
678             }
679             else
680             {
681               cur = new TempPart (enc_type=ENC_YENC, fname);
682               cur->y_line_len = line_len;
683               cur->y_attach_size = attach_size;
684               cur->y_part = part;
685               yenc_looking_for_part_line = cur->y_part!=0;
686             }
687           }
688           else if (state.uu_temp != NULL && is_uu_line(line_str, line_len) )
689           {
690             // continue an incomplete uu decode
691             found = true;
692             // flush the current entry
693             if (cur != NULL) {
694               GMimeStream * s = g_mime_stream_substream (istream, sub_begin, linestart_pos);
695               apply_source_and_maybe_filter (cur, s);
696 
697               if ( append_if_not_present (master, cur) )
698                 append_if_not_present (appendme, cur);
699               cur = NULL;
700             }
701             sub_begin = linestart_pos;
702             cur = state.uu_temp;
703             ++cur->valid_lines;
704             enc_type = ENC_UU;
705           }
706           else if (cur == NULL)
707           {
708             sub_begin = linestart_pos;
709             cur = new TempPart(enc_type);
710 
711           }
712           break;
713         }
714         case ENC_UU:
715         {
716           if (uu_is_ending_line(line_str))
717           {
718             GMimeStream * stream;
719             if (sub_begin < 0)
720               sub_begin = linestart_pos;
721             stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
722             apply_source_and_maybe_filter (cur, stream);
723             if( append_if_not_present (master, cur) )
724               append_if_not_present (appendme, cur);
725 
726             cur = NULL;
727             enc_type = ENC_PLAIN;
728             state.uu_temp = NULL;
729           }
730           else if (!is_uu_line(line_str, line_len))
731           {
732             /* hm, this isn't a uenc line, so ending the cat and setting sub_begin to -1 */
733             if (sub_begin >= 0)
734             {
735               GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
736                 apply_source_and_maybe_filter (cur, stream);
737             }
738 
739             sub_begin = -1;
740           }
741           else if (sub_begin == -1)
742           {
743             /* looks like they decided to start using uu lines again. */
744             ++cur->valid_lines;
745             sub_begin = linestart_pos;
746           }
747           else
748           {
749             ++cur->valid_lines;
750           }
751           break;
752         }
753         case ENC_YENC:
754         {
755           if (yenc_is_ending_line (line_str))
756           {
757             GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos+line_len);
758             apply_source_and_maybe_filter (cur, stream);
759             yenc_parse_end_line (line_str, &cur->y_size, NULL, &cur->y_pcrc, &cur->y_crc);
760             if( append_if_not_present (master, cur) )
761               append_if_not_present (appendme, cur);
762 
763             cur = NULL;
764             enc_type = ENC_PLAIN;
765           }
766           else if (yenc_looking_for_part_line && yenc_is_part_line(line_str))
767           {
768             yenc_parse_part_line (line_str, &cur->y_offset_begin, &cur->y_offset_end);
769             yenc_looking_for_part_line = FALSE;
770             ++cur->valid_lines;
771           }
772           else
773           {
774             ++cur->valid_lines;
775           }
776           break;
777         }
778       }
779     }
780 
781     /* close last entry */
782     if (cur != NULL)
783     {
784       if (sub_begin >= 0)
785       {
786         GMimeStream * stream = g_mime_stream_substream (istream, sub_begin, linestart_pos);
787         apply_source_and_maybe_filter (cur, stream);
788       }
789 
790       /* just in case someone started with "yenc" or "begin 644 asf" in a text message to fuck with unwary newsreaders */
791       if (cur->valid_lines < 10u)
792         cur->type = ENC_PLAIN;
793 
794       if( append_if_not_present (master, cur) )
795         append_if_not_present (appendme, cur);
796       cur = NULL;
797       enc_type = ENC_PLAIN;
798 
799     }
800 
801     g_byte_array_free (line, TRUE);
802     return found;
803   }
804 
805 }
806 
807 void
guess_part_type_from_filename(const char * filename,const char ** setme_type,const char ** setme_subtype)808 mime :: guess_part_type_from_filename (const char   * filename,
809                                        const char  ** setme_type,
810                                        const char  ** setme_subtype)
811 {
812 	static const struct {
813 		const char * suffix;
814 		const char * type;
815 		const char * subtype;
816 	} suffixes[] = {
817 	  { ".asc",   "text",         "plain" }, // plain-encoded signature
818 		{ ".avi",   "video",        "vnd.msvideo" },
819 		{ ".dtd",   "text",         "xml-dtd" },
820 		{ ".flac",  "audio",        "ogg" },
821 		{ ".gif",   "image",        "gif" },
822 		{ ".htm",   "text",         "html" },
823 		{ ".html",  "text",         "html" },
824 		{ ".jpg",   "image",        "jpeg" },
825 		{ ".jpeg",  "image",        "jpeg" },
826 		{ ".md5",   "image",        "tiff" },
827 		{ ".mp3",   "audio",        "mpeg" },
828 		{ ".mpeg",  "video",        "mpeg" },
829 		{ ".mpg",   "video",        "mpeg" },
830 		{ ".mov",   "video",        "quicktime" },
831 		{ ".nfo",   "text",         "plain" },
832 		{ ".oga",   "audio",        "x-vorbis" },
833 		{ ".ogg",   "audio",        "ogg" },
834 		{ ".ogv",   "video",        "ogg" },
835 		{ ".ogx",   "application",  "ogg" },
836 		{ ".png",   "image",        "png" },
837 		{ ".qt",    "video",        "quicktime" },
838 		{ ".rar",   "application",  "x-rar" },
839 		{ ".rv",    "video",        "vnd.rn-realvideo" },
840 		{ ".scr",   "application",  "octet-stream" },
841 		{ ".spx",   "audio",        "ogg" },
842 		{ ".svg",   "image",        "svg+xml" },
843 		{ ".tar",   "application",  "x-tar" },
844 		{ ".tbz2",  "application",  "x-tar" },
845 		{ ".tgz",   "application",  "x-tar" },
846 		{ ".tiff",  "image",        "tiff" },
847 		{ ".tif",   "image",        "tiff" },
848 		{ ".txt",   "text",         "plain" },
849 		{ ".uu",    "text",         "x-uuencode" },
850 		{ ".uue",   "text",         "x-uuencode" },
851 		{ ".xml",   "text",         "xml" },
852 		{ ".xsl",   "text",         "xml" },
853 		{ ".zip",   "application",  "zip" }
854 	};
855 	static const int suffix_qty = G_N_ELEMENTS (suffixes);
856 	const char * suffix;
857 
858 	/* zero out the return values */
859 	pan_return_if_fail (setme_type!=NULL);
860 	pan_return_if_fail (setme_subtype!=NULL);
861 	*setme_type = *setme_subtype = NULL;
862 
863 	/* sanity clause */
864 	pan_return_if_fail (is_nonempty_string(filename));
865 
866        	suffix = strrchr (filename, '.');
867 	if (suffix != NULL) {
868 		int i;
869 		for (i=0; i<suffix_qty; ++i) {
870 			if (!g_ascii_strcasecmp (suffix, suffixes[i].suffix)) {
871 				*setme_type = suffixes[i].type;
872 				*setme_subtype = suffixes[i].subtype;
873 				break;
874 			}
875 		}
876 	}
877 
878 	if (*setme_type == NULL) {
879 		*setme_type = "application";
880 		*setme_subtype = "octet-stream";
881 	}
882 }
883 
884 namespace
885 {
886   void
ptr_array_insert(GPtrArray * array,guint index,gpointer object)887   ptr_array_insert (GPtrArray *array, guint index, gpointer object)
888   {
889     unsigned char *dest, *src;
890     guint n;
891 
892     g_ptr_array_set_size (array, array->len + 1);
893 
894     if (index != array->len) {
895       /* need to move items down */
896       dest = ((unsigned char *) array->pdata) + (sizeof (void *) * (index + 1));
897       src = ((unsigned char *) array->pdata) + (sizeof (void *) * index);
898       n = array->len - index - 1;
899 
900       g_memmove (dest, src, (sizeof (void *) * n));
901     }
902 
903     array->pdata[index] = object;
904   }
905 
906 
handle_encoded_in_text_plain_cb(GMimeObject * parent,GMimeObject * part,gpointer data)907   void handle_encoded_in_text_plain_cb (GMimeObject *parent, GMimeObject *part, gpointer data)
908   {
909 
910     if (!part) return;
911 
912     // we assume that inlined yenc and uu are only in text/plain blocks
913     GMimeContentType * content_type = g_mime_object_get_content_type (part);
914     if (!g_mime_content_type_is_type (content_type, "text", "plain"))
915       return;
916 
917     // get this part's content
918 #ifdef HAVE_GMIME_30
919     GMimeDataWrapper * content = g_mime_part_get_content (GMIME_PART (part));
920 #else
921     GMimeDataWrapper * content = g_mime_part_get_content_object (GMIME_PART (part));
922 #endif
923 
924     if (!content)
925       return;
926 
927     // wrap a buffer stream around it for faster reading -- it could be a file stream
928     GMimeStream * stream = g_mime_data_wrapper_get_stream (content);
929     g_mime_stream_reset (stream);
930     GMimeStream * istream = g_mime_stream_buffer_new (stream, GMIME_STREAM_BUFFER_BLOCK_READ);
931 
932     // break it into separate parts for text, uu, and yenc pieces.
933     sep_state &state(*(sep_state*)data);
934     temp_parts_t &parts(state.current_list);
935     bool split = separate_encoded_parts (istream, state, ENC_PLAIN);
936     g_mime_stream_reset (istream);
937 
938     // split?
939     if(split)
940     {
941       //this part was completely folded into a previous part
942       //so delete it
943       if(parts.size()==0) {
944         GMimeMultipart *mp = GMIME_MULTIPART (parent);
945         int index = g_mime_multipart_index_of (mp,part);
946         if(index>0)
947           g_mime_multipart_remove_at (mp,index);
948         g_object_unref(part);
949       }
950       else
951       {
952         GMimeMultipart * multipart = g_mime_multipart_new_with_subtype ("mixed");
953 
954         const TempPart *tmp_part;
955         const char *filename;
956         GMimePart *subpart;
957         GMimeStream *subpart_stream;
958         foreach (temp_parts_t, parts, it)
959         {
960           // reset these for each part
961           const char * type = "text";
962           const char * subtype = "plain";
963 
964           tmp_part = *it;
965           filename = tmp_part->filename;
966 
967           if (filename && *filename)
968             mime::guess_part_type_from_filename (filename, &type, &subtype);
969 
970           subpart = g_mime_part_new_with_type (type, subtype);
971           if (filename && *filename)
972             g_mime_part_set_filename (subpart, filename);
973 
974           subpart_stream = tmp_part->stream;
975           content = g_mime_data_wrapper_new_with_stream (subpart_stream, GMIME_CONTENT_ENCODING_DEFAULT);
976 #ifdef HAVE_GMIME_30
977           g_mime_part_set_content (subpart, content);
978 #else
979           g_mime_part_set_content_object (subpart, content);
980 #endif
981           g_mime_multipart_add (GMIME_MULTIPART (multipart), GMIME_OBJECT (subpart));
982 
983           g_object_unref (content);
984           g_object_unref (subpart);
985         }
986 
987         // replace the old part with the new multipart
988         GMimeObject *newpart = GMIME_OBJECT(multipart);
989         if(parts.size()==1)
990         {
991           //only one part so no need for multipart
992           newpart = g_mime_multipart_remove_at(multipart,0);
993           g_object_unref(multipart);
994         }
995         if(GMIME_IS_MULTIPART(parent))
996         {
997           GMimeMultipart *mp = GMIME_MULTIPART (parent);
998           int index = g_mime_multipart_index_of (mp, part);
999           g_mime_multipart_remove_at (mp, index);
1000           g_object_unref (part);
1001 
1002           //workaround gmime insert bug
1003           //g_mime_multipart_insert (mp,index,newpart);
1004           {
1005             ptr_array_insert(mp->children, index, newpart);
1006             g_object_ref(newpart);
1007           }
1008         }
1009         else if(GMIME_IS_MESSAGE(parent))
1010         {
1011           g_mime_message_set_mime_part((GMimeMessage*)parent, newpart);
1012         }
1013         g_object_unref(newpart);
1014       }
1015     }
1016 
1017     parts.clear();
1018     g_object_unref (istream);
1019   }
1020 }
1021 
1022 namespace pan
1023 {
1024   struct temp_p {
1025     GMimeObject *parent,*part;
1026 
temp_ppan::temp_p1027     temp_p(GMimeObject *p, GMimeObject *par):parent(p),part(par) {};
1028   };
1029 
1030 #ifdef HAVE_GMIME_CRYPTO
1031   struct QueryMPType
1032   {
1033     GMimeObject* obj;
1034     GPGDecType type;
1035     std::string algo;
1036 
QueryMPTypepan::QueryMPType1037     QueryMPType() : obj(0), type(GPG_DECODE) {}
1038   };
1039 #else
1040    struct QueryMPType
1041   {
1042     GMimeObject* obj;
1043     std::string algo;
1044 
QueryMPTypepan::QueryMPType1045     QueryMPType() : obj(0) {}
1046   };
1047 #endif
1048 }
1049 
1050 namespace
1051 {
1052 
1053   typedef std::vector<temp_p> temp_p_t;
1054 
find_text_cb(GMimeObject * parent,GMimeObject * part,gpointer data)1055   void find_text_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
1056   {
1057     if(!GMIME_IS_PART(part))
1058       return;
1059 
1060     temp_p_t *v( (temp_p_t *) data);
1061     // we assume that inlined yenc and uu are only in text/plain blocks
1062     GMimeContentType * content_type = g_mime_object_get_content_type (part);
1063     if (!g_mime_content_type_is_type (content_type, "text", "plain"))
1064       return;
1065     v->push_back(temp_p(parent,part) );
1066   }
1067 
1068 
mixed_mp_cb(GMimeObject * parent,GMimeObject * part,gpointer data)1069   void mixed_mp_cb(GMimeObject *parent, GMimeObject *part, gpointer data)
1070   {
1071     if(!GMIME_IS_PART(part))
1072       return;
1073 
1074     QueryMPType * type = static_cast<QueryMPType*>(data);
1075 
1076     GMimeContentType * content_type = g_mime_object_get_content_type (part);
1077 //    if (!g_mime_content_type_is_type (content_type, "text", "plain") &&
1078 //        !g_mime_content_type_is_type (content_type, "application", "pgp-signature"))
1079 //      return;
1080 
1081 //    if (g_mime_content_type_is_type (content_type, "application", "pgp-signature"))
1082 //    {
1083 //      g_mime_object_set_content_type_parameter(part, "micalg", "pgp-sha1");//type->algo.c_str());
1084 //      std::cerr<<g_mime_object_to_string(part)<<"\n";
1085 //    }
1086 
1087     g_mime_multipart_add (GMIME_MULTIPART(type->obj), part);
1088   }
1089 
1090 
mixed_mp_find_gpg_params_cb(GMimeObject * parent,GMimeObject * part,gpointer data)1091   void mixed_mp_find_gpg_params_cb (GMimeObject *parent, GMimeObject *part, gpointer data)
1092   {
1093 
1094     if(!GMIME_IS_PART(part))
1095       return;
1096 
1097     QueryMPType * type = static_cast<QueryMPType*>(data);
1098 
1099 //    const char* micalg = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "micalg");
1100 //    if (micalg)
1101 //    {
1102 //      std::cerr<<"micalg found : "<<micalg<<"\n";
1103 //      type->algo = micalg;
1104 //    }
1105 
1106     GMimeContentType * content_type = g_mime_object_get_content_type (part);
1107 
1108     if (!content_type) return;
1109 #ifdef HAVE_GMIME_CRYPTO
1110     if (g_mime_content_type_is_type (content_type, "application", "pgp-signature"))
1111       type->type = GPG_VERIFY;
1112     else if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted"))
1113       type->type = GPG_DECODE;
1114 #endif
1115 
1116   }
1117 
1118 }
1119 
1120 /***
1121 ****
1122 ***/
1123 
1124 #ifdef HAVE_GMIME_CRYPTO
1125 GMimeMessage*
construct_message(GMimeStream ** istreams,int qty,GPGDecErr & err)1126 mime :: construct_message (GMimeStream    ** istreams,
1127                            int               qty,
1128                            GPGDecErr      &  err)
1129 #else
1130 GMimeMessage*
1131 mime :: construct_message (GMimeStream    ** istreams,
1132                            int               qty)
1133 #endif
1134 {
1135   const char * message_id = "Foo <bar@mum>";
1136   GMimeMessage * retval = 0;
1137 
1138   // sanity clause
1139   pan_return_val_if_fail (is_nonempty_string(message_id), NULL);
1140   pan_return_val_if_fail (istreams!=NULL, NULL);
1141   pan_return_val_if_fail (qty != 0, NULL);
1142   for (int i=0; i<qty; ++i)
1143     pan_return_val_if_fail (GMIME_IS_STREAM(istreams[i]), NULL);
1144 
1145   // build the GMimeMessages
1146   GMimeMessage ** messages = g_new (GMimeMessage*, qty);
1147 
1148   for (int i=0; i<qty; ++i) {
1149     GMimeParser* parser = g_mime_parser_new_with_stream (istreams[i]);
1150 #ifdef HAVE_GMIME_30
1151     messages[i] = g_mime_parser_construct_message(parser, NULL);
1152 #else
1153     messages[i] = g_mime_parser_construct_message(parser);
1154 #endif
1155     g_object_unref(parser);
1156     g_mime_stream_reset(istreams[i]);
1157     parser = g_mime_parser_new_with_stream (istreams[i]);
1158 #ifdef HAVE_GMIME_30
1159     GMimeObject* part = g_mime_parser_construct_part(parser, NULL);
1160 #else
1161     GMimeObject* part = g_mime_parser_construct_part(parser);
1162 #endif
1163     g_object_unref (parser);
1164     parser = NULL;
1165     GMimeContentType * type = g_mime_object_get_content_type (part);
1166     const bool multipart (GMIME_IS_MULTIPART_SIGNED(part) || GMIME_IS_MULTIPART_ENCRYPTED(part));
1167 #ifdef HAVE_GMIME_CRYPTO
1168     if (GMIME_IS_MULTIPART_SIGNED(part))
1169     {
1170       err.type = GPG_VERIFY;
1171       err.verify_ok = gpg_verify_mps(part, err);
1172     }
1173     else if (GMIME_IS_MULTIPART_ENCRYPTED(part))
1174     {
1175       err.type = GPG_DECODE;
1176       err.verify_ok =  gpg_verify_mps(part, err);
1177     }
1178 #endif
1179     if (type)
1180     {
1181       if (g_mime_content_type_is_type (type, "multipart", "mixed"))
1182       {
1183         QueryMPType qtype;
1184         g_mime_multipart_foreach (GMIME_MULTIPART(part), mixed_mp_find_gpg_params_cb, &qtype);
1185         GMimeObject* o(0);
1186 #ifdef HAVE_GMIME_CRYPTO
1187         if (qtype.type == GPG_VERIFY)
1188         {
1189           GMimeMultipartSigned* new_mp = g_mime_multipart_signed_new();
1190           err.type = GPG_VERIFY;
1191           qtype.obj = o = GMIME_OBJECT(new_mp);
1192           g_mime_multipart_foreach (GMIME_MULTIPART(part), mixed_mp_cb, &qtype);
1193           err.verify_ok = gpg_verify_mps(GMIME_OBJECT(new_mp), err);
1194           g_mime_message_set_mime_part(messages[i], GMIME_OBJECT(new_mp));
1195         }
1196 
1197         if (qtype.type == GPG_DECODE)
1198         {
1199           GMimeMultipartEncrypted* new_mp = g_mime_multipart_encrypted_new();
1200           err.type = GPG_DECODE;
1201           qtype.obj = o = GMIME_OBJECT(new_mp);
1202           g_mime_multipart_foreach (GMIME_MULTIPART(part), mixed_mp_cb, &qtype);
1203           err.verify_ok =  gpg_verify_mps(GMIME_OBJECT(new_mp), err);
1204           g_mime_message_set_mime_part(messages[i], GMIME_OBJECT(new_mp));
1205         }
1206 #endif
1207         g_object_unref(o);
1208 
1209       }
1210       else if (multipart)
1211       {
1212 #ifdef HAVE_GMIME_CRYPTO
1213         if (err.decrypted)
1214           g_mime_message_set_mime_part(messages[i], err.decrypted);
1215       } else
1216       {
1217 #endif
1218           g_mime_message_set_mime_part(messages[i], part);
1219       }
1220     }
1221 
1222     g_object_unref (part);
1223   }
1224 
1225   if (qty > 1) // fold multiparts together
1226   {
1227     GMimeMultipart * mp = g_mime_multipart_new ();
1228 
1229     for (int i=0; i<qty; ++i) // should this 0 be 1?
1230     {
1231       g_mime_multipart_add(mp,g_mime_message_get_mime_part(messages[i]) );
1232     }
1233 
1234     g_mime_message_set_mime_part(messages[0],GMIME_OBJECT(mp));
1235     g_object_unref(mp);
1236   }
1237 
1238   retval = messages[0];
1239   for (int i=1; i<qty; ++i)
1240     g_object_unref (messages[i]);
1241 
1242   // pick out yenc and uuenc data in text/plain messages
1243   temp_p_t partslist;
1244   sep_state state;
1245   if (retval != NULL)
1246     g_mime_message_foreach(retval, find_text_cb, &partslist);
1247 
1248 
1249   foreach(temp_p_t, partslist, it)
1250   {
1251     temp_p &data(*it);
1252     handle_encoded_in_text_plain_cb(data.parent, data.part, &state);
1253   }
1254 
1255 
1256   // cleanup
1257   foreach (temp_parts_t, state.master_list, it)
1258   {
1259     delete *it;
1260   }
1261   g_free (messages);
1262 
1263   return retval;
1264 }
1265 
1266 /***
1267 ****
1268 ***/
1269 
1270 /**
1271  * Retrieve the charset from a mime message
1272  */
1273 
1274 #if 0 // unused?
1275 static void
1276 get_charset_partfunc (GMimeObject * obj, gpointer charset_gpointer)
1277 {
1278 	GMimePart * part;
1279 	const GMimeContentType * type;
1280 	const char ** charset;
1281 
1282 	if (!GMIME_IS_PART (obj))
1283 		return;
1284 
1285 	part = GMIME_PART (obj);
1286 	type = g_mime_object_get_content_type (GMIME_OBJECT (part));
1287 	charset = (const char **) charset_gpointer;
1288 	if (g_mime_content_type_is_type (type, "text", "*"))
1289 		*charset = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "charset");
1290 }
1291 
1292 const char *
1293 mime :: get_charset (GMimeMessage * message)
1294 {
1295 	const char * retval = NULL;
1296 	pan_return_val_if_fail (message!=NULL, NULL);
1297 
1298 	g_mime_message_foreach_part (message, get_charset_partfunc, &retval);
1299 
1300 	return retval;
1301 }
1302 #endif
1303 
1304 /**
1305 ***
1306 **/
1307 
1308 namespace
1309 {
1310    enum StripFlags
1311    {
1312       STRIP_CASE                    = (1<<0),
1313       STRIP_MULTIPART_NUMERATOR     = (1<<1),
1314       STRIP_MULTIPART               = (1<<2)
1315    };
1316 
1317    /**
1318     * Normalizing a subject header involves:
1319     *
1320     * 1. tearing out the numerator from multipart indicators
1321     *    (i.e., remove "21" from (21/42))
1322     *    for threading.
1323     * 2. convert it to lowercase so that, when sorting, we can
1324     *    have case-insensitive sorting without having to use
1325     *    a slower case-insensitive compare function.
1326     * 3. strip out all the leading noise that breaks sorting.
1327     *
1328     * When we're threading articles, it's much faster to
1329     * normalize the * subjects at the outset instead of
1330     * normalizing them for each comparison.
1331     */
1332    void
normalize_subject(const StringView & subject,StripFlags strip,std::string & setme)1333    normalize_subject (const StringView   & subject,
1334                       StripFlags           strip,
1335                       std::string        & setme)
1336    {
1337 #if 0
1338       static bool _keep_chars[UCHAR_MAX+1];
1339       static bool _inited (false);
1340       if (!_inited) {
1341          _inited = true;
1342          for (int i=0; i<=UCHAR_MAX; ++i) {
1343             const unsigned char uch ((unsigned char)i);
1344             _keep_chars[i] = isalnum(uch) || isdigit(uch) || isspace(uch);
1345          }
1346       }
1347 #endif
1348       setme.reserve (subject.len + 1);
1349       const unsigned char * in ((const unsigned char*) subject.begin());
1350       const unsigned char * end ((const unsigned char*) subject.end());
1351       const bool strip_case (strip & STRIP_CASE);
1352       const bool strip_numerator (strip & STRIP_MULTIPART_NUMERATOR);
1353       const bool strip_multipart (strip & STRIP_MULTIPART);
1354 
1355       // skip the leading noise
1356       while (in!=end && isspace(*in))
1357          ++in;
1358 
1359       while (in!=end)
1360       {
1361          // strip numerator out of multipart information
1362          if ((strip_multipart||strip_numerator)
1363              && (*in=='('||*in=='[')
1364              && isdigit(in[1]))
1365          {
1366             const unsigned char * start (++in);
1367 
1368             if (strip_multipart || strip_numerator)
1369             {
1370                while (in!=end && isdigit(*in))
1371                   ++in;
1372 
1373                if (*in!='/' && *in!='|') // oops, not multipart information
1374                   in = start;
1375 
1376                else if (strip_multipart)
1377                {
1378                   for (++in; in!=end && *in!=']' && *in!=')'; ++in)
1379                   {
1380                      if (isalpha(*in)) { // oops, not multipart information
1381                         in = ++start;
1382                         break;
1383                      }
1384                   }
1385 
1386                   if (in!=end && (*in==']' || *in==')'))
1387                     ++in;
1388                }
1389             }
1390             continue;
1391          }
1392 
1393 #if 0
1394          // strip out junk that breaks sorting
1395          if (_keep_chars[*in])
1396 #endif
1397             setme += (char) (strip_case ? tolower(*in) : *in);
1398 
1399          ++in;
1400       }
1401    }
1402 }
1403 
1404 void
remove_multipart_from_subject(const StringView & subject,std::string & setme)1405 mime :: remove_multipart_from_subject (const StringView    & subject,
1406                                        std::string         & setme)
1407 {
1408   normalize_subject (subject, STRIP_MULTIPART, setme);
1409 }
1410 
1411 void
remove_multipart_part_from_subject(const StringView & subject,std::string & setme)1412 mime :: remove_multipart_part_from_subject (const StringView    & subject,
1413                                             std::string         & setme)
1414 {
1415   normalize_subject (subject, STRIP_MULTIPART_NUMERATOR, setme);
1416 }
1417 
1418 namespace
1419 {
1420   GMimeObject *
1421   handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html);
1422 
1423   GMimeObject *
handle_multipart_alternative(GMimeMultipart * multipart,gboolean * is_html)1424   handle_multipart_alternative (GMimeMultipart *multipart, gboolean *is_html)
1425   {
1426     GMimeObject *mime_part, *text_part = NULL;
1427     GMimeContentType *type;
1428     int count = g_mime_multipart_get_count (multipart);
1429 
1430     for (int i = 0; i < count; ++i) {
1431       mime_part = g_mime_multipart_get_part (multipart, i);
1432 
1433       type = g_mime_object_get_content_type (mime_part);
1434       if (g_mime_content_type_is_type (type, "text", "*")) {
1435         if (!text_part || !g_ascii_strcasecmp (type->subtype, "plain")) {
1436           *is_html = !g_ascii_strcasecmp (type->subtype, "html");
1437           text_part = mime_part;
1438         }
1439       }
1440     }
1441 
1442     return text_part;
1443   }
1444 
1445   GMimeObject *
handle_multipart_mixed(GMimeMultipart * multipart,gboolean * is_html)1446   handle_multipart_mixed (GMimeMultipart *multipart, gboolean *is_html)
1447   {
1448 
1449     GMimeObject *mime_part, *text_part = NULL;
1450     GMimeContentType *type, *first_type = NULL;
1451     int count = g_mime_multipart_get_count (multipart);
1452 
1453     for (int i = 0; i < count; ++i) {
1454       mime_part = g_mime_multipart_get_part (multipart, i);
1455 
1456       type = g_mime_object_get_content_type (mime_part);
1457       if (GMIME_IS_MULTIPART (mime_part)) {
1458         multipart = GMIME_MULTIPART (mime_part);
1459         if (g_mime_content_type_is_type (type, "multipart", "alternative")) {
1460           mime_part = handle_multipart_alternative (multipart, is_html);
1461           if (mime_part)
1462             return mime_part;
1463         } else {
1464           mime_part = handle_multipart_mixed (multipart, is_html);
1465           if (mime_part && !text_part)
1466             text_part = mime_part;
1467         }
1468       } else if (g_mime_content_type_is_type (type, "text", "*")) {
1469         if (!g_ascii_strcasecmp (type->subtype, "plain")) {
1470           /* we got what we came for */
1471           *is_html = !g_ascii_strcasecmp (type->subtype, "html");
1472           return mime_part;
1473         }
1474 
1475         /* if we haven't yet found a text part or if it is a type we can
1476          * understand and it is the first of that type, save it */
1477         if (!text_part || (!g_ascii_strcasecmp (type->subtype, "plain") && (first_type &&
1478                            g_ascii_strcasecmp (type->subtype, first_type->subtype) != 0))) {
1479           *is_html = !g_ascii_strcasecmp (type->subtype, "html");
1480           text_part = mime_part;
1481           first_type = type;
1482         }
1483       }
1484     }
1485 
1486     return text_part;
1487   }
1488 
1489 }
1490 
1491 namespace
1492 {
1493   char *
pan_g_mime_part_get_content(GMimePart * mime_part,size_t * len)1494   pan_g_mime_part_get_content (GMimePart *mime_part, size_t *len)
1495   {
1496     char *retval = NULL;
1497 
1498     g_return_val_if_fail (GMIME_IS_PART (mime_part), NULL);
1499 
1500     if (!mime_part->content || !mime_part->content->stream) {
1501 //      g_warning ("no content set on this mime part");  // dbg
1502       return NULL;
1503     }
1504 
1505 #ifdef HAVE_GMIME_30
1506     GMimeDataWrapper *wrapper = g_mime_part_get_content(mime_part);
1507 #else
1508     GMimeDataWrapper *wrapper = g_mime_part_get_content_object(mime_part);
1509 #endif
1510     GMimeStream *stream = g_mime_stream_mem_new();
1511     g_mime_data_wrapper_write_to_stream (wrapper, stream);
1512     GByteArray *bytes = g_mime_stream_mem_get_byte_array((GMimeStreamMem*)stream);
1513     *len = bytes->len + 1;
1514     if (bytes->len)
1515     {
1516       retval = (char*)g_malloc0(bytes->len + 1);
1517       memcpy(retval, bytes->data, bytes->len);
1518     }
1519     g_object_unref(stream);
1520 
1521     return retval;
1522   }
1523 }
1524 
pan_g_mime_message_get_body(GMimeMessage * message,gboolean * is_html)1525 char *pan::pan_g_mime_message_get_body (GMimeMessage *message, gboolean *is_html)
1526 {
1527   GMimeObject *mime_part = NULL;
1528   GMimeContentType *type;
1529   GMimeMultipart *multipart;
1530   char *body = NULL;
1531   size_t len = 0;
1532 
1533   g_return_val_if_fail (GMIME_IS_MESSAGE (message), NULL);
1534 //  g_return_val_if_fail (is_html != NULL, NULL);
1535 
1536   type = g_mime_object_get_content_type (message->mime_part);
1537   if (GMIME_IS_MULTIPART (message->mime_part)) {
1538     /* let's see if we can find a body in the multipart */
1539     multipart = GMIME_MULTIPART (message->mime_part);
1540     if (g_mime_content_type_is_type (type, "multipart", "alternative"))
1541       mime_part = handle_multipart_alternative (multipart, is_html);
1542     else
1543       mime_part = handle_multipart_mixed (multipart, is_html);
1544   } else if (g_mime_content_type_is_type (type, "text", "*")) {
1545     /* this *has* to be the message body */
1546     if (g_mime_content_type_is_type (type, "text", "html"))
1547       *is_html = TRUE;
1548     else
1549       *is_html = FALSE;
1550     mime_part = message->mime_part;
1551   }
1552 
1553   if (mime_part != NULL) {
1554     body = pan_g_mime_part_get_content (GMIME_PART (mime_part), &len);
1555   }
1556   return body;
1557 }
1558 
1559 #ifdef HAVE_GMIME_30
pan_g_mime_message_add_recipients_from_string(GMimeMessage * message,GMimeAddressType type,const char * string)1560 void pan::pan_g_mime_message_add_recipients_from_string (GMimeMessage *message, GMimeAddressType type, const char *string)
1561 {
1562   InternetAddressList *addrlist;
1563   if ((addrlist = internet_address_list_parse (NULL, string))) {
1564     for (int i = 0; i < internet_address_list_length (addrlist); ++i) {
1565       InternetAddress *ia = internet_address_list_get_address (addrlist, i);
1566       if (INTERNET_ADDRESS_IS_MAILBOX(ia))
1567         g_mime_message_add_mailbox (message, type, internet_address_get_name(ia), internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(ia)));
1568     }
1569   }
1570 }
1571 #else
1572 
pan_g_mime_message_add_recipients_from_string(GMimeMessage * message,GMimeRecipientType type,const char * string)1573 void pan::pan_g_mime_message_add_recipients_from_string (GMimeMessage *message, GMimeRecipientType type, const char *string)
1574 {
1575   InternetAddressList *addrlist;
1576   if ((addrlist = internet_address_list_parse_string (string))) {
1577     for (int i = 0; i < internet_address_list_length (addrlist); ++i) {
1578       InternetAddress *ia = internet_address_list_get_address (addrlist, i);
1579       if (INTERNET_ADDRESS_IS_MAILBOX(ia))
1580         g_mime_message_add_recipient (message, type, internet_address_get_name(ia), internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(ia)));
1581     }
1582   }
1583 }
1584 #endif
1585 
1586 /**
1587 * Works around a GMime bug that uses `Message-Id' rather than `Message-ID'
1588 */
1589 
1590 #ifdef HAVE_GMIME_30
pan_g_mime_message_set_message_id(GMimeMessage * msg,const char * mid)1591 std::string pan::pan_g_mime_message_set_message_id (GMimeMessage *msg, const char *mid)
1592 {
1593     const char * charset = NULL; // "ISO-8859-1";  // FIXME
1594     g_mime_object_append_header ((GMimeObject *) msg, "Message-ID", mid, charset);
1595     char * bracketed = g_strdup_printf ("<%s>", mid);
1596     g_mime_header_list_set (GMIME_OBJECT(msg)->headers, "Message-ID", bracketed, charset);
1597     std::string ret (bracketed);
1598     g_free (bracketed);
1599     return ret;
1600 }
1601 #else
pan_g_mime_message_set_message_id(GMimeMessage * msg,const char * mid)1602 std::string pan::pan_g_mime_message_set_message_id (GMimeMessage *msg, const char *mid)
1603 {
1604     g_mime_object_append_header ((GMimeObject *) msg, "Message-ID", mid);
1605     char * bracketed = g_strdup_printf ("<%s>", mid);
1606     g_mime_header_list_set (GMIME_OBJECT(msg)->headers, "Message-ID", bracketed);
1607     std::string ret (bracketed);
1608     g_free (bracketed);
1609     return ret;
1610 }
1611 #endif
1612 
1613 namespace pan
1614 {
1615 
1616   void
mime_part_set_content(GMimePart * part,const char * str)1617   mime_part_set_content (GMimePart *part, const char *str)
1618   {
1619     GMimeDataWrapper *content;
1620     GMimeStream *stream;
1621 
1622     stream = g_mime_stream_mem_new_with_buffer (str, strlen (str));
1623     content = g_mime_data_wrapper_new_with_stream (stream, GMIME_CONTENT_ENCODING_DEFAULT);
1624     g_object_unref (stream);
1625 
1626 #ifdef HAVE_GMIME_30
1627     g_mime_part_set_content (part, content);
1628 #else
1629     g_mime_part_set_content_object (part, content);
1630 #endif
1631     g_object_unref (content);
1632   }
1633 #ifdef HAVE_GMIME_CRYPTO
1634   GMimeSignatureStatus
get_sig_status(GMimeSignatureList * signatures)1635   get_sig_status (GMimeSignatureList *signatures)
1636   {
1637     GMimeSignatureStatus status = GMIME_SIGNATURE_STATUS_GOOD;
1638     GMimeSignature *sig;
1639     int i;
1640 
1641     if (!signatures || signatures->array->len == 0)
1642       return GMIME_SIGNATURE_STATUS_ERROR;
1643 
1644     for (i = 0; i < g_mime_signature_list_length (signatures); i++) {
1645       sig = g_mime_signature_list_get_signature (signatures, i);
1646       status = MAX (status, sig->status);
1647     }
1648 
1649     return status;
1650   }
1651 
1652   bool
gpg_verify_mps(GMimeObject * obj,GPGDecErr & info)1653   gpg_verify_mps (GMimeObject* obj, GPGDecErr& info)
1654   {
1655 
1656     if (!gpg_inited) return false;
1657 
1658     GMimeMultipartSigned * mps(0);
1659     GMimeMultipartEncrypted * mpe(0);
1660 
1661     switch (info.type)
1662     {
1663       case GPG_VERIFY:
1664         mps = (GMimeMultipartSigned *)obj;
1665         break;
1666 
1667       case GPG_DECODE:
1668         mpe = (GMimeMultipartEncrypted *)obj;
1669         break;
1670     }
1671 
1672     info.clear();
1673 
1674     if (info.type == GPG_VERIFY)
1675     {
1676       GMimeSignatureList * sigs = g_mime_multipart_signed_verify (mps, gpg_ctx, &info.err);
1677       if (info.err || !sigs) return false;
1678       if (sigs) info.no_sigs = false;
1679       fill_signer_info(info.signers, sigs);
1680       bool status = get_sig_status(sigs) == GMIME_SIGNATURE_STATUS_GOOD;
1681       g_object_unref(sigs);
1682       return status;
1683     }
1684 
1685     if (info.type == GPG_DECODE)
1686     {
1687       info.decrypted = g_mime_multipart_encrypted_decrypt (mpe, gpg_ctx, &info.result, &info.err);
1688       if (!info.decrypted)
1689         if (info.err) return false;
1690 
1691       GMimeSignatureList * sigs = g_mime_decrypt_result_get_signatures (info.result);
1692       if (sigs)
1693       {
1694         info.no_sigs = false;
1695         fill_signer_info(info.signers, sigs);
1696         bool status = get_sig_status(info.result->signatures) == GMIME_SIGNATURE_STATUS_GOOD;
1697         g_object_unref(sigs);
1698         return status;
1699       }
1700       return !info.err;
1701     }
1702 
1703     return true;
1704   }
1705 
1706   enum
1707   {
1708     GMIME_MULTIPART_SIGNED_CONTENT,
1709     GMIME_MULTIPART_SIGNED_SIGNATURE
1710   };
1711 
1712   GMimeMessage*
message_add_signed_part(const std::string & uid,const std::string & body_str,GMimeMessage * body)1713   message_add_signed_part (const std::string& uid, const std::string& body_str, GMimeMessage* body)
1714   {
1715     if (uid.empty()) return 0;
1716 
1717     GMimeMultipartSigned *mps;
1718     GError* err(NULL);
1719     GMimePart* part = g_mime_part_new_with_type("text", "plain");
1720     mime_part_set_content (part, body_str.c_str());
1721 
1722     mps = g_mime_multipart_signed_new ();
1723 
1724     /* sign the part */
1725     if (g_mime_multipart_signed_sign (mps, GMIME_OBJECT (part), gpg_ctx, uid.c_str(), GMIME_DIGEST_ALGO_SHA1, &err) <0)
1726     {
1727       g_object_unref(mps);
1728       g_object_unref(G_OBJECT(part));
1729       return 0;
1730     }
1731 
1732     /* GMIME _dirty_ hack : set filename for signature attachment */
1733     /// FIXME : gets scrambled somehow, but _atm_ i don't care ....
1734     GMimeObject * signature = g_mime_multipart_get_part (GMIME_MULTIPART (mps), GMIME_MULTIPART_SIGNED_SIGNATURE);
1735     g_mime_part_set_filename (GMIME_PART(signature), "signature.asc");
1736 
1737     g_mime_message_set_mime_part(body,GMIME_OBJECT(mps));
1738     g_object_unref(G_OBJECT(part));
1739     g_object_unref(mps);
1740 
1741     return body;
1742   }
1743 
1744   bool
gpg_encrypt(const std::string & uid,const std::string & body_str,GMimeMessage * body,GPtrArray * rcp,bool sign)1745   gpg_encrypt (const std::string& uid, const std::string& body_str,
1746                GMimeMessage* body, GPtrArray* rcp, bool sign)
1747   {
1748 
1749     GError* err(0);
1750     GMimePart* part = g_mime_part_new_with_type("text", "plain");
1751     mime_part_set_content (part, body_str.c_str());
1752 
1753     GMimeMultipartEncrypted * mpe = g_mime_multipart_encrypted_new();
1754 
1755     if (g_mime_multipart_encrypted_encrypt(mpe, GMIME_OBJECT (part), gpg_ctx, sign,
1756                                            uid.c_str(), GMIME_DIGEST_ALGO_SHA1, rcp, &err) < 0)
1757     {
1758       g_object_unref(mpe);
1759       g_object_unref(G_OBJECT(part));
1760       return false;
1761     }
1762 
1763     g_mime_message_set_mime_part(body,GMIME_OBJECT(mpe));
1764     g_object_unref(G_OBJECT(part));
1765     g_object_unref(mpe);
1766 
1767     return true;
1768   }
1769 #endif
1770 }
1771 
1772 
1773