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