talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

procmime.c (66423B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      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 #include "defs.h"
     20 
     21 #include <glib.h>
     22 #include <glib/gi18n.h>
     23 #include <string.h>
     24 #include <locale.h>
     25 #include <ctype.h>
     26 #include <stdio.h>
     27 #include <sys/types.h>
     28 #include <sys/stat.h>
     29 #include <unistd.h>
     30 #include <errno.h>
     31 
     32 #include "procmime.h"
     33 #include "procheader.h"
     34 #include "quoted-printable.h"
     35 #include "unmime.h"
     36 #include "html.h"
     37 #include "codeconv.h"
     38 #include "utils.h"
     39 #include "prefs_common.h"
     40 #include "prefs_gtk.h"
     41 #include "alertpanel.h"
     42 #include "account.h"
     43 #include "file-utils.h"
     44 
     45 static GHashTable *procmime_get_mime_type_table	(void);
     46 static MimeInfo *procmime_scan_file_short(const gchar *filename);
     47 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename);
     48 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan);
     49 
     50 MimeInfo *procmime_mimeinfo_new(void)
     51 {
     52 	MimeInfo *mimeinfo;
     53 
     54 	mimeinfo = g_new0(MimeInfo, 1);
     55 
     56 	mimeinfo->content	 = MIMECONTENT_EMPTY;
     57 	mimeinfo->data.filename	 = NULL;
     58 
     59 	mimeinfo->type     	 = MIMETYPE_UNKNOWN;
     60 	mimeinfo->encoding_type  = ENC_UNKNOWN;
     61 	mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
     62 
     63 	mimeinfo->disposition	 = DISPOSITIONTYPE_UNKNOWN;
     64 	mimeinfo->dispositionparameters
     65 				 = g_hash_table_new(g_str_hash, g_str_equal);
     66 
     67 	mimeinfo->node           = g_node_new(mimeinfo);
     68 
     69 	return mimeinfo;
     70 }
     71 
     72 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
     73 {
     74 	g_free(key);
     75 	g_free(value);
     76 
     77 	return TRUE;
     78 }
     79 
     80 static gchar *forced_charset = NULL;
     81 
     82 void procmime_force_charset(const gchar *str)
     83 {
     84 	g_free(forced_charset);
     85 	forced_charset = NULL;
     86 	if (str)
     87 		forced_charset = g_strdup(str);
     88 }
     89 
     90 static EncodingType forced_encoding = 0;
     91 
     92 void procmime_force_encoding(EncodingType encoding)
     93 {
     94 	forced_encoding = encoding;
     95 }
     96 
     97 static gboolean free_func(GNode *node, gpointer data)
     98 {
     99 	MimeInfo *mimeinfo = (MimeInfo *) node->data;
    100 
    101 	switch (mimeinfo->content) {
    102 	case MIMECONTENT_FILE:
    103 		if (mimeinfo->tmp)
    104 			unlink(mimeinfo->data.filename);
    105 		g_free(mimeinfo->data.filename);
    106 		break;
    107 
    108 	case MIMECONTENT_MEM:
    109 		if (mimeinfo->tmp)
    110 			g_free(mimeinfo->data.mem);
    111 	default:
    112 		break;
    113 	}
    114 
    115 	g_free(mimeinfo->subtype);
    116 	g_free(mimeinfo->description);
    117 	g_free(mimeinfo->id);
    118 	g_free(mimeinfo->location);
    119 
    120 	g_hash_table_foreach_remove(mimeinfo->typeparameters,
    121 		procmime_mimeinfo_parameters_destroy, NULL);
    122 	g_hash_table_destroy(mimeinfo->typeparameters);
    123 	g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
    124 		procmime_mimeinfo_parameters_destroy, NULL);
    125 	g_hash_table_destroy(mimeinfo->dispositionparameters);
    126 
    127 	g_free(mimeinfo);
    128 
    129 	return FALSE;
    130 }
    131 
    132 void procmime_mimeinfo_free_all(MimeInfo **mimeinfo_ptr)
    133 {
    134 	MimeInfo *mimeinfo = *mimeinfo_ptr;
    135 	GNode *node;
    136 
    137 	if (!mimeinfo)
    138 		return;
    139 
    140 	node = mimeinfo->node;
    141 	g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
    142 
    143 	g_node_destroy(node);
    144 
    145 	*mimeinfo_ptr = NULL;
    146 }
    147 
    148 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
    149 {
    150 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
    151 	cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
    152 
    153 	if (mimeinfo->node->parent == NULL)
    154 		return NULL;
    155 	return (MimeInfo *) mimeinfo->node->parent->data;
    156 }
    157 
    158 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
    159 {
    160 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
    161 	cm_return_val_if_fail(mimeinfo->node != NULL, NULL);
    162 
    163 	if (mimeinfo->node->children)
    164 		return (MimeInfo *) mimeinfo->node->children->data;
    165 	if (mimeinfo->node->next)
    166 		return (MimeInfo *) mimeinfo->node->next->data;
    167 
    168 	if (mimeinfo->node->parent == NULL)
    169 		return NULL;
    170 
    171 	while (mimeinfo->node->parent != NULL) {
    172 		mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
    173 		if (mimeinfo->node->next)
    174 			return (MimeInfo *) mimeinfo->node->next->data;
    175 	}
    176 
    177 	return NULL;
    178 }
    179 
    180 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
    181 {
    182 	gchar *filename;
    183 	MimeInfo *mimeinfo;
    184 
    185     	filename = procmsg_get_message_file_path(msginfo);
    186 	if (!filename || !is_file_exist(filename)) {
    187 		g_free(filename);
    188 		return NULL;
    189 	}
    190 
    191 	if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
    192 	    !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
    193 		mimeinfo = procmime_scan_file(filename);
    194 	else
    195 		mimeinfo = procmime_scan_queue_file(filename);
    196 	g_free(filename);
    197 
    198 	return mimeinfo;
    199 }
    200 
    201 MimeInfo *procmime_scan_message_short(MsgInfo *msginfo)
    202 {
    203 	gchar *filename;
    204 	MimeInfo *mimeinfo;
    205 
    206 	filename = procmsg_get_message_file_path(msginfo);
    207 	if (!filename || !is_file_exist(filename)) {
    208 		g_free(filename);
    209 		return NULL;
    210 	}
    211 
    212 	if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
    213 	    !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
    214 		mimeinfo = procmime_scan_file_short(filename);
    215 	else
    216 		mimeinfo = procmime_scan_queue_file_short(filename);
    217 	g_free(filename);
    218 
    219 	return mimeinfo;
    220 }
    221 
    222 enum
    223 {
    224 	H_CONTENT_TRANSFER_ENCODING = 0,
    225 	H_CONTENT_TYPE		    = 1,
    226 	H_CONTENT_DISPOSITION	    = 2,
    227 	H_CONTENT_DESCRIPTION	    = 3,
    228 	H_SUBJECT              	    = 4
    229 };
    230 
    231 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
    232 {
    233 	const gchar *value;
    234 
    235 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
    236 	cm_return_val_if_fail(name != NULL, NULL);
    237 
    238 	value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
    239 	if (value == NULL)
    240 		value = g_hash_table_lookup(mimeinfo->typeparameters, name);
    241 
    242 	return value;
    243 }
    244 
    245 #define FLUSH_LASTLINE() {							\
    246 	if (*lastline != '\0') {						\
    247 		gint llen = 0;							\
    248 		strretchomp(lastline);						\
    249 		llen = strlen(lastline);					\
    250 		if (lastline[llen-1] == ' ' &&	\
    251 		    !(llen >= 2 && lastline[1] == ' ' && strchr(prefs_common.quote_chars, lastline[0]))) {					\
    252 			/* this is flowed */					\
    253 			if (delsp)						\
    254 				lastline[llen-1] = '\0';			\
    255 			if (fputs(lastline, outfp) == EOF)			\
    256 				err = TRUE;					\
    257 		} else {							\
    258 			if (fputs(lastline, outfp) == EOF)			\
    259 				err = TRUE;					\
    260 			if (fputs("\n", outfp) == EOF)				\
    261 				err = TRUE;					\
    262 		}								\
    263 	} 									\
    264 	strcpy(lastline, buf);							\
    265 }
    266 
    267 gboolean procmime_decode_content(MimeInfo *mimeinfo)
    268 {
    269 	gchar buf[BUFFSIZE];
    270 	glong readend;
    271 	gchar *tmpfilename;
    272 	FILE *outfp, *infp;
    273 	GStatBuf statbuf;
    274 	gboolean tmp_file = FALSE;
    275 	gboolean flowed = FALSE;
    276 	gboolean delsp = FALSE;
    277 	gboolean err = FALSE;
    278 	gint state = 0;
    279 	guint save = 0;
    280 
    281 	cm_return_val_if_fail(mimeinfo != NULL, FALSE);
    282 
    283 	EncodingType encoding = forced_encoding
    284 				? forced_encoding
    285 				: mimeinfo->encoding_type;
    286 	gchar lastline[BUFFSIZE];
    287 	memset(lastline, 0, BUFFSIZE);
    288 
    289 	if (prefs_common.respect_flowed_format &&
    290 	    mimeinfo->type == MIMETYPE_TEXT &&
    291 	    !strcasecmp(mimeinfo->subtype, "plain")) {
    292 		if (procmime_mimeinfo_get_parameter(mimeinfo, "format") != NULL &&
    293 		    !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "format"),"flowed"))
    294 			flowed = TRUE;
    295 		if (flowed &&
    296 		    procmime_mimeinfo_get_parameter(mimeinfo, "delsp") != NULL &&
    297 		    !strcasecmp(procmime_mimeinfo_get_parameter(mimeinfo, "delsp"),"yes"))
    298 			delsp = TRUE;
    299 	}
    300 
    301 	if (!flowed && (
    302 	     encoding == ENC_UNKNOWN ||
    303 	     encoding == ENC_BINARY ||
    304 	     encoding == ENC_7BIT ||
    305 	     encoding == ENC_8BIT
    306 	    ))
    307 		return TRUE;
    308 
    309 	if (mimeinfo->type == MIMETYPE_MULTIPART || mimeinfo->type == MIMETYPE_MESSAGE)
    310 		return TRUE;
    311 
    312 	if (mimeinfo->data.filename == NULL)
    313 		return FALSE;
    314 
    315 	infp = g_fopen(mimeinfo->data.filename, "rb");
    316 	if (!infp) {
    317 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
    318 		return FALSE;
    319 	}
    320 	if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
    321 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
    322 		fclose(infp);
    323 		return FALSE;
    324 	}
    325 
    326 	outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
    327 	if (!outfp) {
    328 		perror("tmpfile");
    329 		fclose(infp);
    330 		g_free(tmpfilename);
    331 		return FALSE;
    332 	}
    333 
    334 	tmp_file = TRUE;
    335 	readend = mimeinfo->offset + mimeinfo->length;
    336 
    337 	*buf = '\0';
    338 	if (encoding == ENC_QUOTED_PRINTABLE) {
    339 		while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
    340 			size_t len;
    341 			len = qp_decode_line(buf);
    342 			buf[len] = '\0';
    343 			if (!flowed) {
    344 				if (fwrite(buf, 1, len, outfp) < len)
    345 					err = TRUE;
    346 			} else {
    347 				FLUSH_LASTLINE();
    348 			}
    349 		}
    350 		if (flowed)
    351 			FLUSH_LASTLINE();
    352 	} else if (encoding == ENC_BASE64) {
    353 		gchar outbuf[BUFFSIZE + 1];
    354 		glong len;
    355 		gsize inlen, inread;
    356 		gboolean got_error = FALSE;
    357 		gboolean uncanonicalize = FALSE;
    358 		FILE *tmpfp = NULL;
    359 		gboolean null_bytes = FALSE;
    360 		gboolean starting = TRUE;
    361 
    362 		if (mimeinfo->type == MIMETYPE_TEXT ||
    363 		    mimeinfo->type == MIMETYPE_MESSAGE) {
    364 			uncanonicalize = TRUE;
    365 			tmpfp = my_tmpfile();
    366 			if (!tmpfp) {
    367 				perror("my_tmpfile");
    368 				if (tmp_file)
    369 					fclose(outfp);
    370 				fclose(infp);
    371 				g_free(tmpfilename);
    372 				return FALSE;
    373 			}
    374 		} else
    375 			tmpfp = outfp;
    376 
    377 		while ((inlen = MIN(readend - ftell(infp), sizeof(buf))) > 0 && !err) {
    378 			inread = fread(buf, 1, inlen, infp);
    379 			memset(outbuf, 0, sizeof(buf));
    380 			len = (glong)g_base64_decode_step(buf, inlen, outbuf, &state, &save);
    381 			if (uncanonicalize == TRUE && strlen(outbuf) < (size_t)len && starting) {
    382 				uncanonicalize = FALSE;
    383 				null_bytes = TRUE;
    384 			}
    385 			starting = FALSE;
    386 			if (((inread != inlen) || len < 0) && !got_error) {
    387 				g_warning("bad BASE64 content");
    388 				if (fwrite(_("[Error decoding BASE64]\n"),
    389 					sizeof(gchar),
    390 					strlen(_("[Error decoding BASE64]\n")),
    391 					tmpfp) < strlen(_("[Error decoding BASE64]\n")))
    392 					g_warning("error decoding BASE64");
    393 				got_error = TRUE;
    394 				continue;
    395 			} else if (len >= 0) {
    396 				/* print out the error message only once
    397 				 * per block */
    398 				if (null_bytes) {
    399 					/* we won't uncanonicalize, output to outfp directly */
    400 					if (fwrite(outbuf, sizeof(gchar), (size_t)len, outfp) < (size_t)len)
    401 						err = TRUE;
    402 				} else {
    403 					if (fwrite(outbuf, sizeof(gchar), (size_t)len, tmpfp) < (size_t)len)
    404 						err = TRUE;
    405 				}
    406 				got_error = FALSE;
    407 			}
    408 		}
    409 
    410 		if (uncanonicalize) {
    411 			rewind(tmpfp);
    412 			while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
    413 				strcrchomp(buf);
    414 				if (fputs(buf, outfp) == EOF)
    415 					err = TRUE;
    416 			}
    417 		}
    418 		if (tmpfp != outfp) {
    419 			fclose(tmpfp);
    420 		}
    421 	} else {
    422 		while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
    423 			if (!flowed) {
    424 				if (fputs(buf, outfp) == EOF)
    425 					err = TRUE;
    426 			} else {
    427 				FLUSH_LASTLINE();
    428 			}
    429 		}
    430 		if (flowed)
    431 			FLUSH_LASTLINE();
    432 		if (err == TRUE)
    433 			g_warning("write error");
    434 	}
    435 
    436 	fclose(outfp);
    437 	fclose(infp);
    438 
    439 	if (err == TRUE) {
    440 		g_free(tmpfilename);
    441 		return FALSE;
    442 	}
    443 
    444 	if (g_stat(tmpfilename, &statbuf) < 0) {
    445 		FILE_OP_ERROR(tmpfilename, "stat");
    446 		g_free(tmpfilename);
    447 		return FALSE;
    448 	}
    449 
    450 	if (mimeinfo->tmp)
    451 		unlink(mimeinfo->data.filename);
    452 	g_free(mimeinfo->data.filename);
    453 	mimeinfo->data.filename = tmpfilename;
    454 	mimeinfo->tmp = TRUE;
    455 	mimeinfo->offset = 0;
    456 	mimeinfo->length = statbuf.st_size;
    457 	mimeinfo->encoding_type = ENC_BINARY;
    458 
    459 	return TRUE;
    460 }
    461 
    462 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
    463 {
    464 	FILE *infp = NULL, *outfp;
    465 	glong len;
    466 	gchar *tmpfilename;
    467 	GStatBuf statbuf;
    468 	gboolean err = FALSE;
    469 
    470 	if (mimeinfo->content == MIMECONTENT_EMPTY)
    471 		return TRUE;
    472 
    473 	if (mimeinfo->encoding_type != ENC_UNKNOWN &&
    474 	    mimeinfo->encoding_type != ENC_BINARY &&
    475 	    mimeinfo->encoding_type != ENC_7BIT &&
    476 	    mimeinfo->encoding_type != ENC_8BIT)
    477 		if(!procmime_decode_content(mimeinfo))
    478 			return FALSE;
    479 
    480 	outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
    481 	if (!outfp) {
    482 		perror("tmpfile");
    483 		g_free(tmpfilename);
    484 		return FALSE;
    485 	}
    486 
    487 	if (mimeinfo->content == MIMECONTENT_FILE && mimeinfo->data.filename) {
    488 		if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
    489 			g_warning("can't open file %s", mimeinfo->data.filename);
    490 			g_free(tmpfilename);
    491 			fclose(outfp);
    492 			return FALSE;
    493 		}
    494 	} else if (mimeinfo->content == MIMECONTENT_MEM) {
    495 		infp = str_open_as_stream(mimeinfo->data.mem);
    496 		if (infp == NULL) {
    497 			g_free(tmpfilename);
    498 			fclose(outfp);
    499 			return FALSE;
    500 		}
    501 	} else {
    502 		g_free(tmpfilename);
    503 		fclose(outfp);
    504 		g_warning("unknown mimeinfo");
    505 		return FALSE;
    506 	}
    507 
    508 	if (encoding == ENC_BASE64) {
    509 		gchar inbuf[B64_LINE_SIZE], *out;
    510 		FILE *tmp_fp = infp;
    511 		gchar *tmp_file = NULL;
    512 
    513 		if (mimeinfo->type == MIMETYPE_TEXT ||
    514 		     mimeinfo->type == MIMETYPE_MESSAGE) {
    515 		     	if (mimeinfo->content == MIMECONTENT_FILE) {
    516 				tmp_file = get_tmp_file();
    517 				if (canonicalize_file(mimeinfo->data.filename, tmp_file) < 0) {
    518 					g_free(tmp_file);
    519 					g_free(tmpfilename);
    520 					fclose(infp);
    521 					fclose(outfp);
    522 					return FALSE;
    523 				}
    524 				if ((tmp_fp = g_fopen(tmp_file, "rb")) == NULL) {
    525 					FILE_OP_ERROR(tmp_file, "g_fopen");
    526 					unlink(tmp_file);
    527 					g_free(tmp_file);
    528 					g_free(tmpfilename);
    529 					fclose(infp);
    530 					fclose(outfp);
    531 					return FALSE;
    532 				}
    533 			} else {
    534 				gchar *out = canonicalize_str(mimeinfo->data.mem);
    535 				fclose(infp);
    536 				infp = str_open_as_stream(out);
    537 				tmp_fp = infp;
    538 				g_free(out);
    539 				if (infp == NULL) {
    540 					g_free(tmpfilename);
    541 					fclose(outfp);
    542 					return FALSE;
    543 				}
    544 			}
    545 		}
    546 
    547 		while ((len = (glong)fread(inbuf, sizeof(gchar),
    548 				    B64_LINE_SIZE, tmp_fp))
    549 		       == B64_LINE_SIZE) {
    550 			out = g_base64_encode(inbuf, B64_LINE_SIZE);
    551 			if (fputs(out, outfp) == EOF)
    552 				err = TRUE;
    553 			g_free(out);
    554 			if (fputc('\n', outfp) == EOF)
    555 				err = TRUE;
    556 		}
    557 		if (len > 0 && feof(tmp_fp)) {
    558 			out = g_base64_encode(inbuf, (gsize)len);
    559 			if (fputs(out, outfp) == EOF)
    560 				err = TRUE;
    561 			g_free(out);
    562 			if (fputc('\n', outfp) == EOF)
    563 				err = TRUE;
    564 		}
    565 
    566 		if (tmp_file) {
    567 			fclose(tmp_fp);
    568 			unlink(tmp_file);
    569 			g_free(tmp_file);
    570 		}
    571 	} else if (encoding == ENC_QUOTED_PRINTABLE) {
    572 		gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
    573 
    574 		while (fgets(inbuf, sizeof(inbuf), infp) != NULL) {
    575 			qp_encode_line(outbuf, inbuf);
    576 
    577 			if (!strncmp("From ", outbuf, sizeof("From ")-1)) {
    578 				gchar *tmpbuf = outbuf;
    579 
    580 				tmpbuf += sizeof("From ")-1;
    581 
    582 				if (fputs("=46rom ", outfp) == EOF)
    583 					err = TRUE;
    584 				if (fputs(tmpbuf, outfp) == EOF)
    585 					err = TRUE;
    586 			} else {
    587 				if (fputs(outbuf, outfp) == EOF)
    588 					err = TRUE;
    589 			}
    590 		}
    591 	} else {
    592 		gchar buf[BUFFSIZE];
    593 
    594 		while (fgets(buf, sizeof(buf), infp) != NULL) {
    595 			strcrchomp(buf);
    596 			if (fputs(buf, outfp) == EOF)
    597 				err = TRUE;
    598 		}
    599 	}
    600 
    601 	fclose(outfp);
    602 	fclose(infp);
    603 
    604 	if (err == TRUE) {
    605 		g_free(tmpfilename);
    606 		return FALSE;
    607 	}
    608 
    609 	if (mimeinfo->content == MIMECONTENT_FILE) {
    610 		if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
    611 			unlink(mimeinfo->data.filename);
    612 		g_free(mimeinfo->data.filename);
    613 	} else if (mimeinfo->content == MIMECONTENT_MEM) {
    614 		if (mimeinfo->tmp && (mimeinfo->data.mem != NULL))
    615 			g_free(mimeinfo->data.mem);
    616 	}
    617 
    618 	if (g_stat(tmpfilename, &statbuf) < 0) {
    619 		FILE_OP_ERROR(tmpfilename, "stat");
    620 		g_free(tmpfilename);
    621 		return FALSE;
    622 	}
    623 	mimeinfo->content = MIMECONTENT_FILE;
    624 	mimeinfo->data.filename = tmpfilename;
    625 	mimeinfo->tmp = TRUE;
    626 	mimeinfo->offset = 0;
    627 	mimeinfo->length = statbuf.st_size;
    628 	mimeinfo->encoding_type = encoding;
    629 
    630 	return TRUE;
    631 }
    632 
    633 static gint procmime_get_part_to_stream(FILE *outfp, MimeInfo *mimeinfo)
    634 {
    635 	FILE *infp;
    636 	gchar buf[BUFFSIZE];
    637 	glong restlength, readlength;
    638 	gint saved_errno = 0;
    639 
    640 	cm_return_val_if_fail(outfp != NULL, -1);
    641 	cm_return_val_if_fail(mimeinfo != NULL, -1);
    642 
    643 	if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
    644 		return -EINVAL;
    645 
    646 	if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
    647 		saved_errno = errno;
    648 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
    649 		return -(saved_errno);
    650 	}
    651 	if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
    652 		saved_errno = errno;
    653 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
    654 		fclose(infp);
    655 		return -(saved_errno);
    656 	}
    657 
    658 	restlength = mimeinfo->length;
    659 
    660 	while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
    661 		if (fwrite(buf, 1, (size_t)readlength, outfp) != (size_t)readlength) {
    662 			saved_errno = errno;
    663 			fclose(infp);
    664 			return -(saved_errno);
    665 		}
    666 		restlength -= readlength;
    667 	}
    668 
    669 	fclose(infp);
    670 	rewind(outfp);
    671 
    672 	return 0;
    673 }
    674 
    675 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
    676 {
    677 	FILE *outfp;
    678 	gint result;
    679 	gint saved_errno = 0;
    680 
    681 	cm_return_val_if_fail(outfile != NULL, -1);
    682 
    683 	if ((outfp = g_fopen(outfile, "wb")) == NULL) {
    684 		saved_errno = errno;
    685 		FILE_OP_ERROR(outfile, "g_fopen");
    686 		return -(saved_errno);
    687 	}
    688 
    689 	result = procmime_get_part_to_stream(outfp, mimeinfo);
    690 
    691 	if (fclose(outfp) == EOF) {
    692 		saved_errno = errno;
    693 		FILE_OP_ERROR(outfile, "fclose");
    694 		if (unlink(outfile) < 0)
    695                         FILE_OP_ERROR(outfile, "unlink");
    696 		return -(saved_errno);
    697 	}
    698 
    699 	return result;
    700 }
    701 
    702 gboolean procmime_scan_text_content(MimeInfo *mimeinfo,
    703 		gboolean (*scan_callback)(const gchar *str, gpointer cb_data),
    704 		gpointer cb_data)
    705 {
    706 	FILE *tmpfp;
    707 	const gchar *src_codeset;
    708 	gboolean conv_fail = FALSE;
    709 	gchar buf[BUFFSIZE];
    710 	gchar *str;
    711 	gboolean scan_ret = FALSE;
    712 	int r;
    713 
    714 	cm_return_val_if_fail(mimeinfo != NULL, TRUE);
    715 	cm_return_val_if_fail(scan_callback != NULL, TRUE);
    716 
    717 	if (!procmime_decode_content(mimeinfo))
    718 		return TRUE;
    719 
    720 	tmpfp = my_tmpfile();
    721 
    722 	if (tmpfp == NULL) {
    723 		FILE_OP_ERROR("tmpfile", "open");
    724 		return TRUE;
    725 	}
    726 
    727 	if ((r = procmime_get_part_to_stream(tmpfp, mimeinfo)) < 0) {
    728 		g_warning("procmime_get_part_to_stream error %d", r);
    729 		return TRUE;
    730 	}
    731 
    732 	src_codeset = forced_charset
    733 		      ? forced_charset :
    734 		      procmime_mimeinfo_get_parameter(mimeinfo, "charset");
    735 
    736 	/* use supersets transparently when possible */
    737 	if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_ISO_8859_1))
    738 		src_codeset = CS_WINDOWS_1252;
    739 	else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_GBK))
    740 		src_codeset = CS_GB18030;
    741 	else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GBK))
    742 		src_codeset = CS_GB18030;
    743 	else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_GB2312))
    744 		src_codeset = CS_GB18030;
    745 	else if (!forced_charset && src_codeset && !strcasecmp(src_codeset, CS_X_VIET_VPS))
    746 		src_codeset = CS_WINDOWS_874;
    747 
    748 	if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
    749 		SC_HTMLParser *parser;
    750 		CodeConverter *conv;
    751 
    752 		conv = conv_code_converter_new(src_codeset);
    753 		parser = sc_html_parser_new(tmpfp, conv);
    754 		while ((str = sc_html_parse(parser)) != NULL) {
    755 			if ((scan_ret = scan_callback(str, cb_data)) == TRUE)
    756 				break;
    757 		}
    758 		sc_html_parser_destroy(parser);
    759 		conv_code_converter_destroy(conv);
    760 	} else if (mimeinfo->type == MIMETYPE_TEXT && mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
    761 		while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
    762 			str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
    763 			if (str) {
    764 				if ((scan_ret = scan_callback(str, cb_data)) == TRUE) {
    765 					g_free(str);
    766 					break;
    767 				}
    768 				g_free(str);
    769 			} else {
    770 				conv_fail = TRUE;
    771 				if ((scan_ret = scan_callback(buf, cb_data)) == TRUE)
    772 					break;
    773 			}
    774 		}
    775 	}
    776 
    777 	if (conv_fail)
    778 		g_warning("procmime_get_text_content(): code conversion failed");
    779 
    780 	fclose(tmpfp);
    781 
    782 	return scan_ret;
    783 }
    784 
    785 static gboolean scan_fputs_cb(const gchar *str, gpointer fp)
    786 {
    787 	if (fputs(str, (FILE *)fp) == EOF)
    788 		return TRUE;
    789 
    790 	return FALSE;
    791 }
    792 
    793 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
    794 {
    795 	FILE *outfp;
    796 	gboolean err;
    797 
    798 	if ((outfp = my_tmpfile()) == NULL) {
    799 		perror("my_tmpfile");
    800 		return NULL;
    801 	}
    802 
    803 	err = procmime_scan_text_content(mimeinfo, scan_fputs_cb, outfp);
    804 
    805 	rewind(outfp);
    806 	if (err == TRUE) {
    807 		fclose(outfp);
    808 		return NULL;
    809 	}
    810 	return outfp;
    811 
    812 }
    813 
    814 FILE *procmime_get_binary_content(MimeInfo *mimeinfo)
    815 {
    816 	FILE *outfp;
    817 
    818 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
    819 
    820 	if (!procmime_decode_content(mimeinfo))
    821 		return NULL;
    822 
    823 	outfp = my_tmpfile();
    824 
    825 	if (procmime_get_part_to_stream(outfp, mimeinfo) < 0) {
    826 		return NULL;
    827 	}
    828 
    829 	return outfp;
    830 }
    831 
    832 /* search the first text part of (multipart) MIME message,
    833    decode, convert it and output to outfp. */
    834 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
    835 {
    836 	FILE *outfp = NULL;
    837 	MimeInfo *mimeinfo, *partinfo;
    838 	gboolean empty_ok = FALSE, short_scan = TRUE;
    839 
    840     	cm_return_val_if_fail(msginfo != NULL, NULL);
    841 
    842 	/* first we try to short-scan (for speed), refusing empty parts */
    843 scan_again:
    844 	if (short_scan)
    845 		mimeinfo = procmime_scan_message_short(msginfo);
    846 	else
    847 		mimeinfo = procmime_scan_message(msginfo);
    848 	if (!mimeinfo) return NULL;
    849 
    850 	partinfo = mimeinfo;
    851 	while (partinfo && (partinfo->type != MIMETYPE_TEXT ||
    852 	       (partinfo->length == 0 && !empty_ok))) {
    853 		partinfo = procmime_mimeinfo_next(partinfo);
    854 	}
    855 	if (partinfo)
    856 		outfp = procmime_get_text_content(partinfo);
    857 	else if (!empty_ok && short_scan) {
    858 		/* if short scan didn't find a non-empty part, rescan
    859 		 * fully for non-empty parts
    860 		 */
    861 		short_scan = FALSE;
    862 		procmime_mimeinfo_free_all(&mimeinfo);
    863 		goto scan_again;
    864 	} else if (!empty_ok && !short_scan) {
    865 		/* if full scan didn't find a non-empty part, rescan
    866 		 * accepting empty parts
    867 		 */
    868 		empty_ok = TRUE;
    869 		procmime_mimeinfo_free_all(&mimeinfo);
    870 		goto scan_again;
    871 	}
    872 	procmime_mimeinfo_free_all(&mimeinfo);
    873 
    874 	return outfp;
    875 }
    876 
    877 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
    878 {
    879 	static guint32 id = 0;
    880 	gchar *base;
    881 	gchar *filename;
    882 	gchar f_prefix[10];
    883 
    884 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
    885 
    886 	g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
    887 
    888 	if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
    889 		base = g_strdup("mimetmp.html");
    890 	else {
    891 		const gchar *basetmp1;
    892 		gchar *basetmp2;
    893 
    894 		basetmp1 = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
    895 		if (basetmp1 == NULL)
    896 			basetmp1 = procmime_mimeinfo_get_parameter(mimeinfo, "name");
    897 		if (basetmp1 == NULL)
    898 			basetmp1 = "mimetmp";
    899 		basetmp2 = g_path_get_basename(basetmp1);
    900 		if (*basetmp2 == '\0') {
    901 			g_free(basetmp2);
    902 			basetmp2 = g_strdup("mimetmp");
    903 		}
    904 		base = conv_filename_from_utf8(basetmp2);
    905 		g_free(basetmp2);
    906 		subst_for_shellsafe_filename(base);
    907 	}
    908 
    909 	filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
    910 			       f_prefix, base, NULL);
    911 
    912 	g_free(base);
    913 
    914 	return filename;
    915 }
    916 
    917 static GList *mime_type_list = NULL;
    918 
    919 gchar *procmime_get_mime_type(const gchar *filename)
    920 {
    921 	const gchar *p;
    922 	gchar *ext = NULL;
    923 	gchar *base;
    924 	gchar *str;
    925 	static GHashTable *mime_type_table = NULL;
    926 	MimeType *mime_type;
    927 
    928 	if (!mime_type_table) {
    929 		mime_type_table = procmime_get_mime_type_table();
    930 		if (!mime_type_table) return NULL;
    931 	}
    932 
    933 	if (filename == NULL)
    934 		return NULL;
    935 
    936 	base = g_path_get_basename(filename);
    937 	if ((p = strrchr(base, '.')) != NULL)
    938 		ext = g_utf8_strdown(p + 1, -1);
    939 	else
    940 		ext = g_utf8_strdown(base, -1);
    941 	g_free(base);
    942 
    943 	mime_type = g_hash_table_lookup(mime_type_table, ext);
    944 
    945 	if (mime_type) {
    946 		str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
    947 				  NULL);
    948 		debug_print("got type %s for %s\n", str, ext);
    949 		g_free(ext);
    950 		return str;
    951 	}
    952 	g_free(ext);
    953 	return NULL;
    954 }
    955 
    956 
    957 static guint procmime_str_hash(gconstpointer gptr)
    958 {
    959 	guint hash_result = 0;
    960 	const char *str;
    961 
    962 	for (str = gptr; str && *str; str++) {
    963 		if (isupper(*str)) hash_result += (*str + ' ');
    964 		else hash_result += *str;
    965 	}
    966 
    967 	return hash_result;
    968 }
    969 
    970 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
    971 {
    972 	const char *str1 = gptr1;
    973 	const char *str2 = gptr2;
    974 
    975 	return !g_utf8_collate(str1, str2);
    976 }
    977 
    978 static GHashTable *procmime_get_mime_type_table(void)
    979 {
    980 	GHashTable *table = NULL;
    981 	GList *cur;
    982 	MimeType *mime_type;
    983 	gchar **exts;
    984 
    985 	if (!mime_type_list) {
    986 		mime_type_list = procmime_get_mime_type_list();
    987 		if (!mime_type_list) return NULL;
    988 	}
    989 
    990 	table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
    991 
    992 	for (cur = mime_type_list; cur != NULL; cur = cur->next) {
    993 		gint i;
    994 		gchar *key;
    995 
    996 		mime_type = (MimeType *)cur->data;
    997 
    998 		if (!mime_type->extension) continue;
    999 
   1000 		exts = g_strsplit(mime_type->extension, " ", 16);
   1001 		for (i = 0; exts[i] != NULL; i++) {
   1002 			/* Don't overwrite previously inserted extension */
   1003 			if (!g_hash_table_lookup(table, exts[i])) {
   1004 				key = g_strdup(exts[i]);
   1005 				g_hash_table_insert(table, key, mime_type);
   1006 			}
   1007 		}
   1008 		g_strfreev(exts);
   1009 	}
   1010 
   1011 	return table;
   1012 }
   1013 
   1014 GList *procmime_get_mime_type_list(void)
   1015 {
   1016 	GList *list = NULL;
   1017 	FILE *fp;
   1018 	gchar buf[BUFFSIZE];
   1019 	gchar *p;
   1020 	gchar *delim;
   1021 	MimeType *mime_type;
   1022 	gboolean fp_is_glob_file = TRUE;
   1023 
   1024 	if (mime_type_list)
   1025 		return mime_type_list;
   1026 
   1027 	if ((fp = g_fopen("/usr/share/mime/globs", "rb")) == NULL)
   1028 	{
   1029 		fp_is_glob_file = FALSE;
   1030 		if ((fp = g_fopen("/etc/mime.types", "rb")) == NULL) {
   1031 			if ((fp = g_fopen("/usr/share/misc/mime.types", "rb")) == NULL) {
   1032 				FILE_OP_ERROR("/usr/share/misc/mime.types", "g_fopen");
   1033 				return NULL;
   1034 			}
   1035 		}
   1036 	}
   1037 
   1038 	while (fgets(buf, sizeof(buf), fp) != NULL) {
   1039 		p = strchr(buf, '#');
   1040 		if (p) *p = '\0';
   1041 		g_strstrip(buf);
   1042 
   1043 		p = buf;
   1044 
   1045 		if (fp_is_glob_file) {
   1046 			while (*p && !g_ascii_isspace(*p) && (*p!=':')) p++;
   1047 		} else {
   1048 			while (*p && !g_ascii_isspace(*p)) p++;
   1049 		}
   1050 
   1051 		if (*p) {
   1052 			*p = '\0';
   1053 			p++;
   1054 		}
   1055 		delim = strchr(buf, '/');
   1056 		if (delim == NULL) continue;
   1057 		*delim = '\0';
   1058 
   1059 		mime_type = g_new(MimeType, 1);
   1060 		mime_type->type = g_strdup(buf);
   1061 		mime_type->sub_type = g_strdup(delim + 1);
   1062 
   1063 		if (fp_is_glob_file) {
   1064 			while (*p && (g_ascii_isspace(*p)||(*p=='*')||(*p=='.'))) p++;
   1065 		} else {
   1066 			while (*p && g_ascii_isspace(*p)) p++;
   1067 		}
   1068 
   1069 		if (*p)
   1070 			mime_type->extension = g_utf8_strdown(p, -1);
   1071 		else
   1072 			mime_type->extension = NULL;
   1073 
   1074 		list = g_list_append(list, mime_type);
   1075 	}
   1076 
   1077 	fclose(fp);
   1078 
   1079 	if (!list)
   1080 		g_warning("can't read mime.types");
   1081 
   1082 	return list;
   1083 }
   1084 
   1085 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
   1086 {
   1087 	if (!charset)
   1088 		return ENC_8BIT;
   1089 	else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
   1090 		 !g_ascii_strcasecmp(charset, "US-ASCII"))
   1091 		return ENC_7BIT;
   1092 	else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
   1093 		 !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
   1094 		 !g_ascii_strcasecmp(charset, "X-MAC-CYRILLIC") ||
   1095 		 !g_ascii_strcasecmp(charset, "MAC-CYRILLIC") ||
   1096 		 !g_ascii_strcasecmp(charset, "Windows-1251"))
   1097 		return ENC_8BIT;
   1098 	else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
   1099 		return ENC_QUOTED_PRINTABLE;
   1100 	else if (!g_ascii_strncasecmp(charset, "UTF-8", 5))
   1101 		return ENC_QUOTED_PRINTABLE;
   1102 	else
   1103 		return ENC_8BIT;
   1104 }
   1105 
   1106 EncodingType procmime_get_encoding_for_text_file(const gchar *file, gboolean *has_binary)
   1107 {
   1108 	FILE *fp;
   1109 	guchar buf[BUFFSIZE];
   1110 	size_t len;
   1111 	size_t octet_chars = 0;
   1112 	size_t total_len = 0;
   1113 	gfloat octet_percentage;
   1114 	gboolean force_b64 = FALSE;
   1115 
   1116 	if ((fp = g_fopen(file, "rb")) == NULL) {
   1117 		FILE_OP_ERROR(file, "g_fopen");
   1118 		return ENC_UNKNOWN;
   1119 	}
   1120 
   1121 	while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
   1122 		guchar *p;
   1123 		gulong i;
   1124 
   1125 		for (p = buf, i = 0; i < len; ++p, ++i) {
   1126 			if (*p & 0x80)
   1127 				++octet_chars;
   1128 			if (*p == '\0') {
   1129 				force_b64 = TRUE;
   1130 				*has_binary = TRUE;
   1131 			}
   1132 		}
   1133 		total_len += len;
   1134 	}
   1135 
   1136 	fclose(fp);
   1137 
   1138 	if (total_len > 0)
   1139 		octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
   1140 	else
   1141 		octet_percentage = 0.0;
   1142 
   1143 	debug_print("procmime_get_encoding_for_text_file(): "
   1144 		    "8bit chars: %"G_GSIZE_FORMAT" / %"G_GSIZE_FORMAT" (%f%%)\n", octet_chars, total_len,
   1145 		    100.0 * octet_percentage);
   1146 
   1147 	if (octet_percentage > 0.20 || force_b64) {
   1148 		debug_print("using BASE64\n");
   1149 		return ENC_BASE64;
   1150 	} else if (octet_chars > 0) {
   1151 		debug_print("using quoted-printable\n");
   1152 		return ENC_QUOTED_PRINTABLE;
   1153 	} else {
   1154 		debug_print("using 7bit\n");
   1155 		return ENC_7BIT;
   1156 	}
   1157 }
   1158 
   1159 struct EncodingTable
   1160 {
   1161 	gchar *str;
   1162 	EncodingType enc_type;
   1163 };
   1164 
   1165 struct EncodingTable encoding_table[] = {
   1166 	{"7bit", ENC_7BIT},
   1167 	{"8bit", ENC_8BIT},
   1168 	{"binary", ENC_BINARY},
   1169 	{"quoted-printable", ENC_QUOTED_PRINTABLE},
   1170 	{"base64", ENC_BASE64},
   1171 	{"x-uuencode", ENC_UNKNOWN},
   1172 	{NULL, ENC_UNKNOWN},
   1173 };
   1174 
   1175 const gchar *procmime_get_encoding_str(EncodingType encoding)
   1176 {
   1177 	struct EncodingTable *enc_table;
   1178 
   1179 	for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
   1180 		if (enc_table->enc_type == encoding)
   1181 			return enc_table->str;
   1182 	}
   1183 	return NULL;
   1184 }
   1185 
   1186 /* --- NEW MIME STUFF --- */
   1187 struct TypeTable
   1188 {
   1189 	gchar *str;
   1190 	MimeMediaType type;
   1191 };
   1192 
   1193 static struct TypeTable mime_type_table[] = {
   1194 	{"text", MIMETYPE_TEXT},
   1195 	{"image", MIMETYPE_IMAGE},
   1196 	{"audio", MIMETYPE_AUDIO},
   1197 	{"video", MIMETYPE_VIDEO},
   1198 	{"font",  MIMETYPE_FONT},
   1199 	{"model", MIMETYPE_MODEL},
   1200 	{"chemical", MIMETYPE_CHEMICAL},
   1201 	{"application", MIMETYPE_APPLICATION},
   1202 	{"message", MIMETYPE_MESSAGE},
   1203 	{"multipart", MIMETYPE_MULTIPART},
   1204 	{NULL, 0},
   1205 };
   1206 
   1207 const gchar *procmime_get_media_type_str(MimeMediaType type)
   1208 {
   1209 	struct TypeTable *type_table;
   1210 
   1211 	for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
   1212 		if (type_table->type == type)
   1213 			return type_table->str;
   1214 	}
   1215 	return NULL;
   1216 }
   1217 
   1218 MimeMediaType procmime_get_media_type(const gchar *str)
   1219 {
   1220 	struct TypeTable *typetablearray;
   1221 
   1222 	for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
   1223 		if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
   1224 			return typetablearray->type;
   1225 
   1226 	return MIMETYPE_UNKNOWN;
   1227 }
   1228 
   1229 /*!
   1230  *\brief	Safe wrapper for content type string.
   1231  *
   1232  *\return	const gchar * Pointer to content type string.
   1233  */
   1234 gchar *procmime_get_content_type_str(MimeMediaType type,
   1235 					   const char *subtype)
   1236 {
   1237 	const gchar *type_str = NULL;
   1238 
   1239 	if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
   1240 		return g_strdup("unknown");
   1241 	return g_strdup_printf("%s/%s", type_str, subtype);
   1242 }
   1243 
   1244 static int procmime_parse_mimepart(MimeInfo *parent,
   1245 			     gchar *content_type,
   1246 			     gchar *content_encoding,
   1247 			     gchar *content_description,
   1248 			     gchar *content_id,
   1249 			     gchar *content_disposition,
   1250 			     gchar *content_location,
   1251 			     const gchar *original_msgid,
   1252 			     const gchar *disposition_notification_hdr,
   1253 			     const gchar *filename,
   1254 			     guint offset,
   1255 			     guint length,
   1256 			     gboolean short_scan);
   1257 
   1258 static void procmime_parse_message_rfc822(MimeInfo *mimeinfo, gboolean short_scan)
   1259 {
   1260 	HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
   1261 			        {"Content-Transfer-Encoding:",
   1262 			  			   NULL, FALSE},
   1263 				{"Content-Description:",
   1264 						   NULL, TRUE},
   1265 			        {"Content-ID:",
   1266 						   NULL, TRUE},
   1267 				{"Content-Disposition:",
   1268 				                   NULL, TRUE},
   1269 				{"Content-Location:",
   1270 						   NULL, TRUE},
   1271 				{"MIME-Version:",
   1272 						   NULL, TRUE},
   1273 				{"Original-Message-ID:",
   1274 						   NULL, TRUE},
   1275 				{"Disposition:",
   1276 						   NULL, TRUE},
   1277 				{NULL,		   NULL, FALSE}};
   1278 	glong content_start;
   1279 	guint i;
   1280 	FILE *fp;
   1281         gchar *tmp;
   1282 	glong len = 0;
   1283 
   1284 	procmime_decode_content(mimeinfo);
   1285 
   1286 	fp = g_fopen(mimeinfo->data.filename, "rb");
   1287 	if (fp == NULL) {
   1288 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   1289 		return;
   1290 	}
   1291 	if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
   1292 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   1293 		fclose(fp);
   1294 		return;
   1295 	}
   1296 	procheader_get_header_fields(fp, hentry);
   1297 	if (hentry[0].body != NULL) {
   1298 		tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);
   1299                 g_free(hentry[0].body);
   1300                 hentry[0].body = tmp;
   1301         }
   1302 	if (hentry[2].body != NULL) {
   1303 		tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);
   1304                 g_free(hentry[2].body);
   1305                 hentry[2].body = tmp;
   1306         }
   1307 	if (hentry[4].body != NULL) {
   1308 		tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);
   1309                 g_free(hentry[4].body);
   1310                 hentry[4].body = tmp;
   1311         }
   1312 	if (hentry[5].body != NULL) {
   1313 		tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);
   1314                 g_free(hentry[5].body);
   1315                 hentry[5].body = tmp;
   1316         }
   1317 	if (hentry[7].body != NULL) {
   1318 		tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);
   1319                 g_free(hentry[7].body);
   1320                 hentry[7].body = tmp;
   1321         }
   1322 	if (hentry[8].body != NULL) {
   1323 		tmp = conv_unmime_header(hentry[8].body, NULL, FALSE);
   1324                 g_free(hentry[8].body);
   1325                 hentry[8].body = tmp;
   1326         }
   1327 
   1328 	content_start = ftell(fp);
   1329 	fclose(fp);
   1330 
   1331 	len = mimeinfo->length - (content_start - mimeinfo->offset);
   1332 	if (len < 0)
   1333 		len = 0;
   1334 	procmime_parse_mimepart(mimeinfo,
   1335 				hentry[0].body, hentry[1].body,
   1336 				hentry[2].body, hentry[3].body,
   1337 				hentry[4].body, hentry[5].body,
   1338 				hentry[7].body, hentry[8].body,
   1339 				mimeinfo->data.filename, content_start,
   1340 				len, short_scan);
   1341 
   1342 	for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
   1343 		g_free(hentry[i].body);
   1344 		hentry[i].body = NULL;
   1345 	}
   1346 }
   1347 
   1348 static void procmime_parse_disposition_notification(MimeInfo *mimeinfo,
   1349 		const gchar *original_msgid, const gchar *disposition_notification_hdr,
   1350 		gboolean short_scan)
   1351 {
   1352 	HeaderEntry hentry[] = {{"Original-Message-ID:",  NULL, TRUE},
   1353 			        {"Disposition:",	  NULL, TRUE},
   1354 				{NULL,			  NULL, FALSE}};
   1355 	guint i;
   1356 	FILE *fp;
   1357 	gchar *orig_msg_id = NULL;
   1358 	gchar *disp = NULL;
   1359 
   1360 	procmime_decode_content(mimeinfo);
   1361 
   1362 	debug_print("parse disposition notification\n");
   1363 	fp = g_fopen(mimeinfo->data.filename, "rb");
   1364 	if (fp == NULL) {
   1365 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   1366 		return;
   1367 	}
   1368 	if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
   1369 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   1370 		fclose(fp);
   1371 		return;
   1372 	}
   1373 
   1374 	if (original_msgid && disposition_notification_hdr) {
   1375 		hentry[0].body = g_strdup(original_msgid);
   1376 		hentry[1].body = g_strdup(disposition_notification_hdr);
   1377 	} else {
   1378 		procheader_get_header_fields(fp, hentry);
   1379 	}
   1380 
   1381         fclose(fp);
   1382 
   1383     	if (!hentry[0].body || !hentry[1].body) {
   1384 		debug_print("MsgId %s, Disp %s\n",
   1385 			hentry[0].body ? hentry[0].body:"(nil)",
   1386 			hentry[1].body ? hentry[1].body:"(nil)");
   1387 		goto bail;
   1388 	}
   1389 
   1390 	orig_msg_id = g_strdup(hentry[0].body);
   1391 	disp = g_strdup(hentry[1].body);
   1392 
   1393 	extract_parenthesis(orig_msg_id, '<', '>');
   1394 	remove_space(orig_msg_id);
   1395 
   1396 	if (strstr(disp, "displayed")) {
   1397 		/* find sent message, if possible */
   1398 		MsgInfo *info = NULL;
   1399 		GList *flist;
   1400 		debug_print("%s has been displayed.\n", orig_msg_id);
   1401 		for (flist = folder_get_list(); flist != NULL; flist = g_list_next(flist)) {
   1402 			FolderItem *outbox = ((Folder *)(flist->data))->outbox;
   1403 			if (!outbox) {
   1404 				debug_print("skipping folder with no outbox...\n");
   1405 				continue;
   1406 			}
   1407 			info = folder_item_get_msginfo_by_msgid(outbox, orig_msg_id);
   1408 			debug_print("%s %s in %s\n", info?"found":"didn't find", orig_msg_id, outbox->path);
   1409 			if (info) {
   1410 				procmsg_msginfo_set_flags(info, MSG_RETRCPT_GOT, 0);
   1411 				procmsg_msginfo_free(&info);
   1412 			}
   1413 		}
   1414 	}
   1415 	g_free(orig_msg_id);
   1416 	g_free(disp);
   1417 bail:
   1418 	for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
   1419 		g_free(hentry[i].body);
   1420 		hentry[i].body = NULL;
   1421 	}
   1422 }
   1423 
   1424 #define GET_HEADERS() {						\
   1425 	procheader_get_header_fields(fp, hentry);		\
   1426         if (hentry[0].body != NULL) {				\
   1427 		tmp = conv_unmime_header(hentry[0].body, NULL, FALSE);	\
   1428                 g_free(hentry[0].body);				\
   1429                 hentry[0].body = tmp;				\
   1430         }                					\
   1431         if (hentry[2].body != NULL) {				\
   1432 		tmp = conv_unmime_header(hentry[2].body, NULL, FALSE);	\
   1433                 g_free(hentry[2].body);				\
   1434                 hentry[2].body = tmp;				\
   1435         }                					\
   1436         if (hentry[4].body != NULL) {				\
   1437 		tmp = conv_unmime_header(hentry[4].body, NULL, FALSE);	\
   1438                 g_free(hentry[4].body);				\
   1439                 hentry[4].body = tmp;				\
   1440         }                					\
   1441         if (hentry[5].body != NULL) {				\
   1442 		tmp = conv_unmime_header(hentry[5].body, NULL, FALSE);	\
   1443                 g_free(hentry[5].body);				\
   1444                 hentry[5].body = tmp;				\
   1445         }                					\
   1446 	if (hentry[6].body != NULL) {				\
   1447 		tmp = conv_unmime_header(hentry[6].body, NULL, FALSE);	\
   1448                 g_free(hentry[6].body);				\
   1449                 hentry[6].body = tmp;				\
   1450         }                					\
   1451 	if (hentry[7].body != NULL) {				\
   1452 		tmp = conv_unmime_header(hentry[7].body, NULL, FALSE);	\
   1453                 g_free(hentry[7].body);				\
   1454                 hentry[7].body = tmp;				\
   1455         }							\
   1456 }
   1457 
   1458 static void procmime_parse_multipart(MimeInfo *mimeinfo, gboolean short_scan)
   1459 {
   1460 	HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
   1461 			        {"Content-Transfer-Encoding:",
   1462 			  			   NULL, FALSE},
   1463 				{"Content-Description:",
   1464 						   NULL, TRUE},
   1465 			        {"Content-ID:",
   1466 						   NULL, TRUE},
   1467 				{"Content-Disposition:",
   1468 				                   NULL, TRUE},
   1469 				{"Content-Location:",
   1470 						   NULL, TRUE},
   1471 				{"Original-Message-ID:",
   1472 						   NULL, TRUE},
   1473 				{"Disposition:",
   1474 						   NULL, TRUE},
   1475 				{NULL,		   NULL, FALSE}};
   1476 	gchar *tmp;
   1477 	gchar *boundary;
   1478 	gsize boundary_len = 0;
   1479 	glong lastoffset = -1;
   1480 	gulong i;
   1481 	gchar buf[BUFFSIZE];
   1482 	FILE *fp;
   1483 	int result = 0;
   1484 	gboolean start_found = FALSE;
   1485 	gboolean end_found = FALSE;
   1486 
   1487 	boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
   1488 	if (!boundary)
   1489 		return;
   1490 	boundary_len = strlen(boundary);
   1491 
   1492 	procmime_decode_content(mimeinfo);
   1493 
   1494 	fp = g_fopen(mimeinfo->data.filename, "rb");
   1495 	if (fp == NULL) {
   1496 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   1497 		return;
   1498 	}
   1499 
   1500 	if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
   1501 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   1502 		fclose(fp);
   1503 		return;
   1504 	}
   1505 
   1506 	while (fgets(buf, sizeof(buf), fp) != NULL && result == 0) {
   1507 		if (ftell(fp) - 1 > (mimeinfo->offset + mimeinfo->length))
   1508 			break;
   1509 
   1510 		if (IS_BOUNDARY(buf, boundary, boundary_len)) {
   1511 			start_found = TRUE;
   1512 
   1513 			if (lastoffset != -1) {
   1514 				glong len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
   1515 				if (len < 0)
   1516 					len = 0;
   1517 				result = procmime_parse_mimepart(mimeinfo,
   1518 				                        hentry[0].body, hentry[1].body,
   1519 							hentry[2].body, hentry[3].body,
   1520 							hentry[4].body, hentry[5].body,
   1521 							hentry[6].body, hentry[7].body,
   1522 							mimeinfo->data.filename, lastoffset,
   1523 							len, short_scan);
   1524 				if (result == 1 && short_scan)
   1525 					break;
   1526 
   1527 			}
   1528 
   1529 			if (buf[2 + boundary_len]     == '-' &&
   1530 			    buf[2 + boundary_len + 1] == '-') {
   1531 			    	end_found = TRUE;
   1532 				break;
   1533 			}
   1534 			for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
   1535 				g_free(hentry[i].body);
   1536 				hentry[i].body = NULL;
   1537 			}
   1538 			GET_HEADERS();
   1539 			lastoffset = ftell(fp);
   1540 		}
   1541 	}
   1542 
   1543 	if (start_found && !end_found && lastoffset != -1) {
   1544 		glong len = (ftell(fp) - strlen(buf)) - lastoffset - 1;
   1545 
   1546 		if (len >= 0) {
   1547 			result = procmime_parse_mimepart(mimeinfo,
   1548 				        hentry[0].body, hentry[1].body,
   1549 					hentry[2].body, hentry[3].body,
   1550 					hentry[4].body, hentry[5].body,
   1551 					hentry[6].body, hentry[7].body,
   1552 					mimeinfo->data.filename, lastoffset,
   1553 					len, short_scan);
   1554 		}
   1555 		mimeinfo->broken = TRUE;
   1556 	}
   1557 
   1558 	for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
   1559 		g_free(hentry[i].body);
   1560 		hentry[i].body = NULL;
   1561 	}
   1562 	fclose(fp);
   1563 }
   1564 
   1565 static void parse_parameters(const gchar *parameters, GHashTable *table)
   1566 {
   1567 	gchar *params, *param, *next;
   1568 	GSList *convlist = NULL, *concatlist = NULL, *cur;
   1569 
   1570 	params = g_strdup(parameters);
   1571 	param = params;
   1572 	next = params;
   1573 	for (; next != NULL; param = next) {
   1574 		gchar *attribute, *value, *tmp, *down_attr, *orig_down_attr;
   1575 		glong len;
   1576 		gboolean convert = FALSE;
   1577 
   1578 		next = strchr_with_skip_quote(param, '"', ';');
   1579 		if (next != NULL) {
   1580 			next[0] = '\0';
   1581 			next++;
   1582 		}
   1583 
   1584 		g_strstrip(param);
   1585 
   1586 		attribute = param;
   1587 		value = strchr(attribute, '=');
   1588 		if (value == NULL)
   1589 			continue;
   1590 
   1591 		value[0] = '\0';
   1592 		value++;
   1593 		while (value[0] != '\0' && value[0] == ' ')
   1594 			value++;
   1595 
   1596 		down_attr = g_utf8_strdown(attribute, -1);
   1597 		orig_down_attr = down_attr;
   1598 
   1599 		len = down_attr ? strlen(down_attr):0;
   1600 		if (len > 0 && down_attr[len - 1] == '*') {
   1601 			gchar *srcpos, *dstpos, *endpos;
   1602 
   1603 			convert = TRUE;
   1604 			down_attr[len - 1] = '\0';
   1605 
   1606 			srcpos = value;
   1607 			dstpos = value;
   1608 			endpos = value + strlen(value);
   1609 			while (srcpos < endpos) {
   1610 				if (*srcpos != '%')
   1611 					*dstpos = *srcpos;
   1612 				else {
   1613 					guchar dstvalue;
   1614 
   1615 					if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
   1616 						*dstpos = '?';
   1617 					else
   1618 						*dstpos = dstvalue;
   1619 					srcpos += 2;
   1620 				}
   1621 				srcpos++;
   1622 				dstpos++;
   1623 			}
   1624 			*dstpos = '\0';
   1625 			if (value[0] == '"')
   1626 				extract_quote(value, '"');
   1627 		} else {
   1628 			if (value[0] == '"')
   1629 				extract_quote(value, '"');
   1630 			else if ((tmp = strchr(value, ' ')) != NULL)
   1631 				*tmp = '\0';
   1632 		}
   1633 
   1634 		if (down_attr) {
   1635 			while (down_attr[0] == ' ')
   1636 				down_attr++;
   1637 			while (down_attr[strlen(down_attr)-1] == ' ')
   1638 				down_attr[strlen(down_attr)-1] = '\0';
   1639 		}
   1640 
   1641 		while (value[0] != '\0' && value[0] == ' ')
   1642 			value++;
   1643 		while (value[strlen(value)-1] == ' ')
   1644 			value[strlen(value)-1] = '\0';
   1645 
   1646 		if (down_attr && strrchr(down_attr, '*') != NULL) {
   1647 			gchar *tmpattr;
   1648 
   1649 			tmpattr = g_strdup(down_attr);
   1650 			tmp = strrchr(tmpattr, '*');
   1651 			tmp[0] = '\0';
   1652 
   1653 			if ((tmp[1] == '0') && (tmp[2] == '\0') &&
   1654 			    (g_slist_find_custom(concatlist, down_attr, (GCompareFunc)g_strcmp0) == NULL))
   1655 				concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
   1656 
   1657 			if (convert && (g_slist_find_custom(convlist, tmpattr, (GCompareFunc)g_strcmp0) == NULL))
   1658 				convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
   1659 
   1660 			g_free(tmpattr);
   1661 		} else if (convert) {
   1662 			if (g_slist_find_custom(convlist, down_attr, (GCompareFunc)g_strcmp0) == NULL)
   1663 				convlist = g_slist_prepend(convlist, g_strdup(down_attr));
   1664 		}
   1665 
   1666 		if (g_hash_table_lookup(table, down_attr) == NULL)
   1667 			g_hash_table_insert(table, g_strdup(down_attr), g_strdup(value));
   1668 		g_free(orig_down_attr);
   1669 	}
   1670 
   1671 	for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
   1672 		gchar *attribute, *attrwnum, *partvalue;
   1673 		gint n = 0;
   1674 		GString *value;
   1675 
   1676 		attribute = (gchar *) cur->data;
   1677 		value = g_string_sized_new(64);
   1678 
   1679 		attrwnum = g_strdup_printf("%s*%d", attribute, n);
   1680 		while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
   1681 			g_string_append(value, partvalue);
   1682 
   1683 			g_hash_table_remove(table, attrwnum);
   1684 			g_free(attrwnum);
   1685 			n++;
   1686 			attrwnum = g_strdup_printf("%s*%d", attribute, n);
   1687 		}
   1688 		g_free(attrwnum);
   1689 
   1690 		g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
   1691 		g_string_free(value, TRUE);
   1692 	}
   1693 	slist_free_strings_full(concatlist);
   1694 
   1695 	for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
   1696 		gchar *attribute, *key, *value;
   1697 		gchar *charset, *lang, *oldvalue, *newvalue;
   1698 
   1699 		attribute = (gchar *) cur->data;
   1700 		if (!g_hash_table_lookup_extended(
   1701 			table, attribute, (gpointer *)(gchar *) &key, (gpointer *)(gchar *) &value))
   1702 			continue;
   1703 
   1704 		charset = value;
   1705 		if (charset == NULL)
   1706 			continue;
   1707 		lang = strchr(charset, '\'');
   1708 		if (lang == NULL)
   1709 			continue;
   1710 		lang[0] = '\0';
   1711 		lang++;
   1712 		oldvalue = strchr(lang, '\'');
   1713 		if (oldvalue == NULL)
   1714 			continue;
   1715 		oldvalue[0] = '\0';
   1716 		oldvalue++;
   1717 
   1718 		newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
   1719 
   1720 		g_hash_table_remove(table, attribute);
   1721 		g_free(key);
   1722 		g_free(value);
   1723 
   1724 		g_hash_table_insert(table, g_strdup(attribute), newvalue);
   1725 	}
   1726 	slist_free_strings_full(convlist);
   1727 
   1728 	g_free(params);
   1729 }
   1730 
   1731 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
   1732 {
   1733 	cm_return_if_fail(content_type != NULL);
   1734 	cm_return_if_fail(mimeinfo != NULL);
   1735 
   1736 	/* RFC 2045, page 13 says that the mime subtype is MANDATORY;
   1737 	 * if it's not available we use the default Content-Type */
   1738 	if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
   1739 		mimeinfo->type = MIMETYPE_TEXT;
   1740 		mimeinfo->subtype = g_strdup("plain");
   1741 		if (g_hash_table_lookup(mimeinfo->typeparameters,
   1742 				       "charset") == NULL) {
   1743 			g_hash_table_insert(mimeinfo->typeparameters,
   1744 				    g_strdup("charset"),
   1745 				    g_strdup(
   1746 					conv_get_locale_charset_str_no_utf8()));
   1747 		}
   1748 	} else {
   1749 		gchar *type, *subtype, *params;
   1750 
   1751 		type = g_strdup(content_type);
   1752 		subtype = strchr(type, '/') + 1;
   1753 		*(subtype - 1) = '\0';
   1754 		if ((params = strchr(subtype, ';')) != NULL) {
   1755 			params[0] = '\0';
   1756 			params++;
   1757 		}
   1758 
   1759 		mimeinfo->type = procmime_get_media_type(type);
   1760 		mimeinfo->subtype = g_strstrip(g_strdup(subtype));
   1761 
   1762 		/* Get mimeinfo->typeparameters */
   1763 		if (params != NULL)
   1764 			parse_parameters(params, mimeinfo->typeparameters);
   1765 
   1766 		g_free(type);
   1767 	}
   1768 }
   1769 
   1770 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
   1771 {
   1772 	gchar *tmp, *params;
   1773 
   1774 	cm_return_if_fail(content_disposition != NULL);
   1775 	cm_return_if_fail(mimeinfo != NULL);
   1776 
   1777 	tmp = g_strdup(content_disposition);
   1778 	if ((params = strchr(tmp, ';')) != NULL) {
   1779 		params[0] = '\0';
   1780 		params++;
   1781 	}
   1782 	g_strstrip(tmp);
   1783 
   1784 	if (!g_ascii_strcasecmp(tmp, "inline"))
   1785 		mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
   1786 	else if (!g_ascii_strcasecmp(tmp, "attachment"))
   1787 		mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
   1788 	else
   1789 		mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
   1790 
   1791 	if (params != NULL)
   1792 		parse_parameters(params, mimeinfo->dispositionparameters);
   1793 
   1794 	g_free(tmp);
   1795 }
   1796 
   1797 
   1798 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
   1799 {
   1800 	struct EncodingTable *enc_table;
   1801 
   1802 	for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
   1803 		if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
   1804 			mimeinfo->encoding_type = enc_table->enc_type;
   1805 			return;
   1806 		}
   1807 	}
   1808 	mimeinfo->encoding_type = ENC_UNKNOWN;
   1809 	return;
   1810 }
   1811 
   1812 static GSList *registered_parsers = NULL;
   1813 
   1814 static MimeParser *procmime_get_mimeparser_for_type(MimeMediaType type, const gchar *sub_type)
   1815 {
   1816 	GSList *cur;
   1817 	for (cur = registered_parsers; cur; cur = cur->next) {
   1818 		MimeParser *parser = (MimeParser *)cur->data;
   1819 		if (parser->type == type && !g_strcmp0(parser->sub_type, sub_type))
   1820 			return parser;
   1821 	}
   1822 	return NULL;
   1823 }
   1824 
   1825 void procmime_mimeparser_register(MimeParser *parser)
   1826 {
   1827 	if (!procmime_get_mimeparser_for_type(parser->type, parser->sub_type))
   1828 		registered_parsers = g_slist_append(registered_parsers, parser);
   1829 }
   1830 
   1831 
   1832 void procmime_mimeparser_unregister(MimeParser *parser)
   1833 {
   1834 	registered_parsers = g_slist_remove(registered_parsers, parser);
   1835 }
   1836 
   1837 static gboolean procmime_mimeparser_parse(MimeParser *parser, MimeInfo *mimeinfo)
   1838 {
   1839 	cm_return_val_if_fail(parser->parse != NULL, FALSE);
   1840 	return parser->parse(parser, mimeinfo);
   1841 }
   1842 
   1843 static int procmime_parse_mimepart(MimeInfo *parent,
   1844 			     gchar *content_type,
   1845 			     gchar *content_encoding,
   1846 			     gchar *content_description,
   1847 			     gchar *content_id,
   1848 			     gchar *content_disposition,
   1849 			     gchar *content_location,
   1850 			     const gchar *original_msgid,
   1851 			     const gchar *disposition_notification_hdr,
   1852 			     const gchar *filename,
   1853 			     guint offset,
   1854 			     guint length,
   1855 			     gboolean short_scan)
   1856 {
   1857 	MimeInfo *mimeinfo;
   1858 	MimeParser *parser = NULL;
   1859 	gboolean parsed = FALSE;
   1860 	int result = 0;
   1861 
   1862 	/* Create MimeInfo */
   1863 	mimeinfo = procmime_mimeinfo_new();
   1864 	mimeinfo->content = MIMECONTENT_FILE;
   1865 
   1866 	if (parent != NULL) {
   1867 		if (g_node_depth(parent->node) > 32) {
   1868 			/* 32 is an arbitrary value
   1869 			 * this avoids DOSsing ourselves
   1870 			 * with enormous messages
   1871 			 */
   1872 			procmime_mimeinfo_free_all(&mimeinfo);
   1873 			return -1;
   1874 		}
   1875 		g_node_append(parent->node, mimeinfo->node);
   1876 	}
   1877 	mimeinfo->data.filename = g_strdup(filename);
   1878 	mimeinfo->offset = offset;
   1879 	mimeinfo->length = length;
   1880 
   1881 	if (content_type != NULL) {
   1882 		g_strchomp(content_type);
   1883 		procmime_parse_content_type(content_type, mimeinfo);
   1884 	} else {
   1885 		mimeinfo->type = MIMETYPE_TEXT;
   1886 		mimeinfo->subtype = g_strdup("plain");
   1887 		if (g_hash_table_lookup(mimeinfo->typeparameters,
   1888 				       "charset") == NULL) {
   1889 			g_hash_table_insert(mimeinfo->typeparameters,
   1890 				    g_strdup("charset"),
   1891 				    g_strdup(
   1892 					conv_get_locale_charset_str_no_utf8()));
   1893 		}
   1894 	}
   1895 
   1896 	if (content_encoding != NULL) {
   1897  		g_strchomp(content_encoding);
   1898 		procmime_parse_content_encoding(content_encoding, mimeinfo);
   1899 	} else {
   1900 		mimeinfo->encoding_type = ENC_UNKNOWN;
   1901 	}
   1902 
   1903 	if (content_description != NULL)
   1904 		mimeinfo->description = g_strdup(content_description);
   1905 	else
   1906 		mimeinfo->description = NULL;
   1907 
   1908 	if (content_id != NULL)
   1909 		mimeinfo->id = g_strdup(content_id);
   1910 	else
   1911 		mimeinfo->id = NULL;
   1912 
   1913 	if (content_location != NULL)
   1914 		mimeinfo->location = g_strdup(content_location);
   1915 	else
   1916 		mimeinfo->location = NULL;
   1917 
   1918 	if (content_disposition != NULL) {
   1919  		g_strchomp(content_disposition);
   1920 		procmime_parse_content_disposition(content_disposition, mimeinfo);
   1921 	} else
   1922 		mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
   1923 
   1924 	/* Call parser for mime type */
   1925 	if ((parser = procmime_get_mimeparser_for_type(mimeinfo->type, mimeinfo->subtype)) != NULL) {
   1926 		parsed = procmime_mimeparser_parse(parser, mimeinfo);
   1927 	}
   1928 	if (!parsed) {
   1929 		switch (mimeinfo->type) {
   1930 		case MIMETYPE_TEXT:
   1931 			if (g_ascii_strcasecmp(mimeinfo->subtype, "plain") == 0 && short_scan) {
   1932 				return 1;
   1933 			}
   1934 			break;
   1935 
   1936 		case MIMETYPE_MESSAGE:
   1937 			if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
   1938 				procmime_parse_message_rfc822(mimeinfo, short_scan);
   1939 			}
   1940 			if (g_ascii_strcasecmp(mimeinfo->subtype, "disposition-notification") == 0) {
   1941 				procmime_parse_disposition_notification(mimeinfo,
   1942 					original_msgid, disposition_notification_hdr, short_scan);
   1943 			}
   1944 			break;
   1945 
   1946 		case MIMETYPE_MULTIPART:
   1947 			procmime_parse_multipart(mimeinfo, short_scan);
   1948 			break;
   1949 
   1950 		case MIMETYPE_APPLICATION:
   1951 			if (g_ascii_strcasecmp(mimeinfo->subtype, "octet-stream") == 0
   1952 			&& original_msgid && *original_msgid
   1953 			&& disposition_notification_hdr && *disposition_notification_hdr) {
   1954 				procmime_parse_disposition_notification(mimeinfo,
   1955 					original_msgid, disposition_notification_hdr, short_scan);
   1956 			}
   1957 			break;
   1958 		default:
   1959 			break;
   1960 		}
   1961 	}
   1962 
   1963 	return result;
   1964 }
   1965 
   1966 static gchar *typenames[] = {
   1967     "text",
   1968     "image",
   1969     "audio",
   1970     "video",
   1971     "application",
   1972     "message",
   1973     "multipart",
   1974     "font",
   1975     "model",
   1976     "chemical",
   1977     "unknown",
   1978 };
   1979 
   1980 static gboolean output_func(GNode *node, gpointer data)
   1981 {
   1982 	guint i, depth;
   1983 	MimeInfo *mimeinfo = (MimeInfo *) node->data;
   1984 
   1985 	depth = g_node_depth(node);
   1986 	for (i = 0; i < depth; i++)
   1987 		g_print("    ");
   1988 	g_print("%s/%s (offset:%ld length:%ld encoding: %d)\n",
   1989 		(mimeinfo->type <= MIMETYPE_UNKNOWN)? typenames[mimeinfo->type] : "unknown",
   1990 		mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
   1991 
   1992 	return FALSE;
   1993 }
   1994 
   1995 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
   1996 {
   1997 	g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
   1998 }
   1999 
   2000 static MimeInfo *procmime_scan_file_with_offset(const gchar *filename, glong offset, gboolean short_scan)
   2001 {
   2002 	MimeInfo *mimeinfo;
   2003 	GStatBuf buf;
   2004 
   2005 	if (g_stat(filename, &buf) < 0) {
   2006 		FILE_OP_ERROR(filename, "stat");
   2007 		return NULL;
   2008 	}
   2009 
   2010 	mimeinfo = procmime_mimeinfo_new();
   2011 	mimeinfo->content = MIMECONTENT_FILE;
   2012 	mimeinfo->encoding_type = ENC_UNKNOWN;
   2013 	mimeinfo->type = MIMETYPE_MESSAGE;
   2014 	mimeinfo->subtype = g_strdup("rfc822");
   2015 	mimeinfo->data.filename = g_strdup(filename);
   2016 	mimeinfo->offset = offset;
   2017 	mimeinfo->length = buf.st_size - offset;
   2018 
   2019 	procmime_parse_message_rfc822(mimeinfo, short_scan);
   2020 	if (debug_get_mode())
   2021 		output_mime_structure(mimeinfo, 0);
   2022 
   2023 	return mimeinfo;
   2024 }
   2025 
   2026 static MimeInfo *procmime_scan_file_full(const gchar *filename, gboolean short_scan)
   2027 {
   2028 	MimeInfo *mimeinfo;
   2029 
   2030 	cm_return_val_if_fail(filename != NULL, NULL);
   2031 
   2032 	mimeinfo = procmime_scan_file_with_offset(filename, 0, short_scan);
   2033 
   2034 	return mimeinfo;
   2035 }
   2036 
   2037 MimeInfo *procmime_scan_file(const gchar *filename)
   2038 {
   2039 	return procmime_scan_file_full(filename, FALSE);
   2040 }
   2041 
   2042 static MimeInfo *procmime_scan_file_short(const gchar *filename)
   2043 {
   2044 	return procmime_scan_file_full(filename, TRUE);
   2045 }
   2046 
   2047 static MimeInfo *procmime_scan_queue_file_full(const gchar *filename, gboolean short_scan)
   2048 {
   2049 	FILE *fp;
   2050 	MimeInfo *mimeinfo;
   2051 	gchar buf[BUFFSIZE];
   2052 	glong offset = 0;
   2053 
   2054 	cm_return_val_if_fail(filename != NULL, NULL);
   2055 
   2056 	/* Open file */
   2057 	if ((fp = g_fopen(filename, "rb")) == NULL)
   2058 		return NULL;
   2059 	/* Skip queue header */
   2060 	while (fgets(buf, sizeof(buf), fp) != NULL) {
   2061 		/* new way */
   2062 		if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
   2063 			strlen("X-Claws-End-Special-Headers:"))) ||
   2064 		   (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
   2065 			strlen("X-Sylpheed-End-Special-Headers:"))))
   2066 			break;
   2067 		/* old way */
   2068 		if (buf[0] == '\r' || buf[0] == '\n') break;
   2069 		/* from other mailers */
   2070 		if (!strncmp(buf, "Date: ", 6)
   2071 		||  !strncmp(buf, "To: ", 4)
   2072 		||  !strncmp(buf, "From: ", 6)
   2073 		||  !strncmp(buf, "Subject: ", 9)) {
   2074 			rewind(fp);
   2075 			break;
   2076 		}
   2077 	}
   2078 	offset = ftell(fp);
   2079 	fclose(fp);
   2080 
   2081 	mimeinfo = procmime_scan_file_with_offset(filename, offset, short_scan);
   2082 
   2083 	return mimeinfo;
   2084 }
   2085 
   2086 MimeInfo *procmime_scan_queue_file(const gchar *filename)
   2087 {
   2088 	return procmime_scan_queue_file_full(filename, FALSE);
   2089 }
   2090 
   2091 static MimeInfo *procmime_scan_queue_file_short(const gchar *filename)
   2092 {
   2093 	return procmime_scan_queue_file_full(filename, TRUE);
   2094 }
   2095 
   2096 typedef enum {
   2097     ENC_AS_TOKEN,
   2098     ENC_AS_QUOTED_STRING,
   2099     ENC_AS_EXTENDED,
   2100     ENC_AS_ENCWORD
   2101 } EncodeAs;
   2102 
   2103 typedef struct _ParametersData {
   2104 	FILE *fp;
   2105 	guint len;
   2106 	gint error;
   2107 } ParametersData;
   2108 
   2109 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
   2110 {
   2111 	gchar *param = key;
   2112 	gchar *val = value, *valpos, *tmp;
   2113 	ParametersData *pdata = (ParametersData *)user_data;
   2114 	GString *buf = g_string_new("");
   2115 	glong len;
   2116 
   2117 	EncodeAs encas = ENC_AS_TOKEN;
   2118 
   2119 	for (valpos = val; *valpos != 0; valpos++) {
   2120 		if (!IS_ASCII(*valpos)) {
   2121 			encas = ENC_AS_ENCWORD;
   2122 			break;
   2123 		}
   2124 
   2125 		/* CTLs */
   2126 		if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
   2127 			encas = ENC_AS_QUOTED_STRING;
   2128 			continue;
   2129 		}
   2130 
   2131 		/* tspecials + SPACE */
   2132 		switch (*valpos) {
   2133 		case ' ':
   2134 		case '(':
   2135 		case ')':
   2136 		case '<':
   2137 		case '>':
   2138 		case '@':
   2139         	case ',':
   2140 		case ';':
   2141 		case ':':
   2142 		case '\\':
   2143 		case '"':
   2144         	case '/':
   2145 		case '[':
   2146 		case ']':
   2147 		case '?':
   2148 		case '=':
   2149 			encas = ENC_AS_QUOTED_STRING;
   2150 			continue;
   2151 		}
   2152 	}
   2153 
   2154 	switch (encas) {
   2155 	case ENC_AS_TOKEN:
   2156 		g_string_append_printf(buf, "%s=%s", param, val);
   2157 		break;
   2158 
   2159 	case ENC_AS_QUOTED_STRING:
   2160 		g_string_append_printf(buf, "%s=\"%s\"", param, val);
   2161 		break;
   2162 
   2163 #if 0 /* we don't use that for now */
   2164 	case ENC_AS_EXTENDED:
   2165 		if (!g_utf8_validate(val, -1, NULL))
   2166 			g_string_append_printf(buf, "%s*=%s''", param,
   2167 				conv_get_locale_charset_str());
   2168 		else
   2169 			g_string_append_printf(buf, "%s*=%s''", param,
   2170 				CS_INTERNAL);
   2171 		for (valpos = val; *valpos != '\0'; valpos++) {
   2172 			if (IS_ASCII(*valpos) && isalnum(*valpos)) {
   2173 				g_string_append_printf(buf, "%c", *valpos);
   2174 			} else {
   2175 				gchar hexstr[3] = "XX";
   2176 				get_hex_str(hexstr, *valpos);
   2177 				g_string_append_printf(buf, "%%%s", hexstr);
   2178 			}
   2179 		}
   2180 		break;
   2181 #else
   2182 	case ENC_AS_EXTENDED:
   2183 		debug_print("Unhandled ENC_AS_EXTENDED.\n");
   2184 		break;
   2185 #endif
   2186 	case ENC_AS_ENCWORD:
   2187 		len = MAX(strlen(val)*6, 512);
   2188 		tmp = g_malloc(len+1);
   2189 		codeconv_set_strict(TRUE);
   2190 		conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
   2191 			prefs_common.outgoing_charset);
   2192 		codeconv_set_strict(FALSE);
   2193 		if (!tmp || !*tmp) {
   2194 			codeconv_set_strict(TRUE);
   2195 			conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
   2196 				conv_get_outgoing_charset_str());
   2197 			codeconv_set_strict(FALSE);
   2198 		}
   2199 		if (!tmp || !*tmp) {
   2200 			codeconv_set_strict(TRUE);
   2201 			conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
   2202 				CS_UTF_8);
   2203 			codeconv_set_strict(FALSE);
   2204 		}
   2205 		if (!tmp || !*tmp) {
   2206 			conv_encode_header_full(tmp, len, val, pdata->len + strlen(param) + 4 , FALSE,
   2207 				CS_UTF_8);
   2208 		}
   2209 		g_string_append_printf(buf, "%s=\"%s\"", param, tmp);
   2210 		g_free(tmp);
   2211 		break;
   2212 
   2213 	}
   2214 
   2215 	if (buf->str && strlen(buf->str)) {
   2216 		tmp = strstr(buf->str, "\n");
   2217 		if (tmp)
   2218 			len = (tmp - buf->str);
   2219 		else
   2220 			len = strlen(buf->str);
   2221 		if (pdata->len + len > 76) {
   2222 			if (fprintf(pdata->fp, ";\n %s", buf->str) < 0)
   2223 				pdata->error = TRUE;
   2224 			pdata->len = strlen(buf->str) + 1;
   2225 		} else {
   2226 			if (fprintf(pdata->fp, "; %s", buf->str) < 0)
   2227 				pdata->error = TRUE;
   2228 			pdata->len += strlen(buf->str) + 2;
   2229 		}
   2230 	}
   2231 	g_string_free(buf, TRUE);
   2232 }
   2233 
   2234 #define TRY(func) { \
   2235 	if (!(func)) { \
   2236 		return -1; \
   2237 	} \
   2238 }
   2239 
   2240 int procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
   2241 {
   2242 	struct TypeTable *type_table;
   2243 	ParametersData *pdata = g_new0(ParametersData, 1);
   2244 	debug_print("procmime_write_mime_header\n");
   2245 
   2246 	pdata->fp = fp;
   2247 	pdata->error = FALSE;
   2248 	for (type_table = mime_type_table; type_table->str != NULL; type_table++)
   2249 		if (mimeinfo->type == type_table->type) {
   2250 			gchar *buf = g_strdup_printf(
   2251 				"Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
   2252 			if (fprintf(fp, "%s", buf) < 0) {
   2253 				g_free(buf);
   2254 				g_free(pdata);
   2255 				return -1;
   2256 			}
   2257 			pdata->len = strlen(buf);
   2258 			g_free(buf);
   2259 			break;
   2260 		}
   2261 	g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
   2262 	if (pdata->error == TRUE) {
   2263 		g_free(pdata);
   2264 		return -1;
   2265 	}
   2266 	g_free(pdata);
   2267 
   2268 	TRY(fprintf(fp, "\n") >= 0);
   2269 
   2270 	if (mimeinfo->encoding_type != ENC_UNKNOWN)
   2271 		TRY(fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type)) >= 0);
   2272 
   2273 	if (mimeinfo->description != NULL)
   2274 		TRY(fprintf(fp, "Content-Description: %s\n", mimeinfo->description) >= 0);
   2275 
   2276 	if (mimeinfo->id != NULL)
   2277 		TRY(fprintf(fp, "Content-ID: %s\n", mimeinfo->id) >= 0);
   2278 
   2279 	if (mimeinfo->location != NULL)
   2280 		TRY(fprintf(fp, "Content-Location: %s\n", mimeinfo->location) >= 0);
   2281 
   2282 	if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
   2283 		ParametersData *pdata = g_new0(ParametersData, 1);
   2284 		gchar *buf = NULL;
   2285 		if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
   2286 			buf = g_strdup("Content-Disposition: inline");
   2287 		else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
   2288 			buf = g_strdup("Content-Disposition: attachment");
   2289 		else
   2290 			buf = g_strdup("Content-Disposition: unknown");
   2291 
   2292 		if (fprintf(fp, "%s", buf) < 0) {
   2293 			g_free(buf);
   2294 			g_free(pdata);
   2295 			return -1;
   2296 		}
   2297 		pdata->len = strlen(buf);
   2298 		g_free(buf);
   2299 
   2300 		pdata->fp = fp;
   2301 		pdata->error = FALSE;
   2302 		g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
   2303 		if (pdata->error == TRUE) {
   2304 			g_free(pdata);
   2305 			return -1;
   2306 		}
   2307 		g_free(pdata);
   2308 		TRY(fprintf(fp, "\n") >= 0);
   2309 	}
   2310 
   2311 	TRY(fprintf(fp, "\n") >= 0);
   2312 
   2313 	return 0;
   2314 }
   2315 
   2316 static gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
   2317 {
   2318 	FILE *infp;
   2319 	GNode *childnode;
   2320 	MimeInfo *child;
   2321 	gchar buf[BUFFSIZE];
   2322 	gboolean skip = FALSE;
   2323 	size_t len;
   2324 
   2325 	debug_print("procmime_write_message_rfc822\n");
   2326 
   2327 	/* write header */
   2328 	switch (mimeinfo->content) {
   2329 	case MIMECONTENT_FILE:
   2330 		if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
   2331 			FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   2332 			return -1;
   2333 		}
   2334 		if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
   2335 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   2336 			fclose(infp);
   2337 			return -1;
   2338 		}
   2339 		while (fgets(buf, sizeof(buf), infp) == buf) {
   2340 			strcrchomp(buf);
   2341 			if (buf[0] == '\n' && buf[1] == '\0')
   2342 				break;
   2343 			if (skip && (buf[0] == ' ' || buf[0] == '\t'))
   2344 				continue;
   2345 			if (g_ascii_strncasecmp(buf, "MIME-Version:", 13) == 0 ||
   2346 			    g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
   2347 			    g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
   2348 			    g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
   2349 			    g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
   2350 			    g_ascii_strncasecmp(buf, "Content-Location:", 17) == 0 ||
   2351 			    g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
   2352 				skip = TRUE;
   2353 				continue;
   2354 			}
   2355 			len = strlen(buf);
   2356 			if (fwrite(buf, sizeof(gchar), len, fp) < len) {
   2357 				g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from file", len);
   2358 				fclose(infp);
   2359 				return -1;
   2360 			}
   2361 			skip = FALSE;
   2362 		}
   2363 		fclose(infp);
   2364 		break;
   2365 
   2366 	case MIMECONTENT_MEM:
   2367 		len = strlen(mimeinfo->data.mem);
   2368 		if (fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len) {
   2369 			g_warning("failed to dump %"G_GSIZE_FORMAT" bytes from mem", len);
   2370 			return -1;
   2371 		}
   2372 		break;
   2373 
   2374 	default:
   2375 		break;
   2376 	}
   2377 
   2378 	childnode = mimeinfo->node->children;
   2379 	if (childnode == NULL)
   2380 		return -1;
   2381 
   2382 	child = (MimeInfo *) childnode->data;
   2383 	if (fprintf(fp, "MIME-Version: 1.0\n") < 0) {
   2384 		g_warning("failed to write mime version");
   2385 		return -1;
   2386 	}
   2387 	if (procmime_write_mime_header(child, fp) < 0)
   2388 		return -1;
   2389 	return procmime_write_mimeinfo(child, fp);
   2390 }
   2391 
   2392 static gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
   2393 {
   2394 	FILE *infp;
   2395 	GNode *childnode;
   2396 	gchar *boundary, *str, *str2;
   2397 	gchar buf[BUFFSIZE];
   2398 	gboolean firstboundary;
   2399 	size_t len;
   2400 
   2401 	debug_print("procmime_write_multipart\n");
   2402 
   2403 	boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
   2404 
   2405 	switch (mimeinfo->content) {
   2406 	case MIMECONTENT_FILE:
   2407 		if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
   2408 			FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   2409 			return -1;
   2410 		}
   2411 		if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
   2412 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   2413 			fclose(infp);
   2414 			return -1;
   2415 		}
   2416 		while (fgets(buf, sizeof(buf), infp) == buf) {
   2417 			if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
   2418 				break;
   2419 			len = strlen(buf);
   2420 			if (fwrite(buf, sizeof(gchar), len, fp) < len) {
   2421 				g_warning("failed to write %"G_GSIZE_FORMAT, len);
   2422 				fclose(infp);
   2423 				return -1;
   2424 			}
   2425 		}
   2426 		fclose(infp);
   2427 		break;
   2428 
   2429 	case MIMECONTENT_MEM:
   2430 		str = g_strdup(mimeinfo->data.mem);
   2431 		if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
   2432 		    (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
   2433 			*(str2 - 2) = '\0';
   2434 		len = strlen(str);
   2435 		if (fwrite(str, sizeof(gchar), len, fp) < len) {
   2436 			g_warning("failed to write %"G_GSIZE_FORMAT" from mem", len);
   2437 			g_free(str);
   2438 			return -1;
   2439 		}
   2440 		g_free(str);
   2441 		break;
   2442 
   2443 	default:
   2444 		break;
   2445 	}
   2446 
   2447 	childnode = mimeinfo->node->children;
   2448 	firstboundary = TRUE;
   2449 	while (childnode != NULL) {
   2450 		MimeInfo *child = childnode->data;
   2451 
   2452 		if (firstboundary)
   2453 			firstboundary = FALSE;
   2454 		else
   2455 			TRY(fprintf(fp, "\n") >= 0);
   2456 
   2457 		TRY(fprintf(fp, "--%s\n", boundary) >= 0);
   2458 
   2459 		if (procmime_write_mime_header(child, fp) < 0)
   2460 			return -1;
   2461 		if (procmime_write_mimeinfo(child, fp) < 0)
   2462 			return -1;
   2463 
   2464 		childnode = g_node_next_sibling(childnode);
   2465 	}
   2466 	TRY(fprintf(fp, "\n--%s--\n", boundary) >= 0);
   2467 
   2468 	return 0;
   2469 }
   2470 
   2471 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
   2472 {
   2473 	FILE *infp;
   2474 	size_t len;
   2475 	debug_print("procmime_write_mimeinfo\n");
   2476 
   2477 	if (G_NODE_IS_LEAF(mimeinfo->node)) {
   2478 		switch (mimeinfo->content) {
   2479 		case MIMECONTENT_FILE:
   2480 			if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
   2481 				FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   2482 				return -1;
   2483 			}
   2484 			copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
   2485 			fclose(infp);
   2486 			return 0;
   2487 
   2488 		case MIMECONTENT_MEM:
   2489 			len = strlen(mimeinfo->data.mem);
   2490 			if (fwrite(mimeinfo->data.mem, sizeof(gchar), len, fp) < len)
   2491 				return -1;
   2492 			return 0;
   2493 
   2494 		default:
   2495 			return 0;
   2496 		}
   2497 	} else {
   2498 		/* Call writer for mime type */
   2499 		switch (mimeinfo->type) {
   2500 		case MIMETYPE_MESSAGE:
   2501 			if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
   2502 				return procmime_write_message_rfc822(mimeinfo, fp);
   2503 			}
   2504 			break;
   2505 
   2506 		case MIMETYPE_MULTIPART:
   2507 			return procmime_write_multipart(mimeinfo, fp);
   2508 
   2509 		default:
   2510 			break;
   2511 		}
   2512 
   2513 		return -1;
   2514 	}
   2515 
   2516 	return 0;
   2517 }
   2518 
   2519 gchar *procmime_get_part_file_name(MimeInfo *mimeinfo)
   2520 {
   2521 	gchar *base;
   2522 
   2523 	if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
   2524 		base = g_strdup("mimetmp.html");
   2525 	else {
   2526 		const gchar *basetmp;
   2527 		gchar *basename;
   2528 
   2529 		basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
   2530 		if (basetmp == NULL)
   2531 			basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
   2532 		if (basetmp == NULL)
   2533 			basetmp = "mimetmp";
   2534 		basename = g_path_get_basename(basetmp);
   2535 		if (*basename == '\0') {
   2536 			g_free(basename);
   2537 			basename = g_strdup("mimetmp");
   2538 		}
   2539 		base = conv_filename_from_utf8(basename);
   2540 		g_free(basename);
   2541 		subst_for_shellsafe_filename(base);
   2542 	}
   2543 
   2544 	return base;
   2545 }
   2546 
   2547 void *procmime_get_part_as_string(MimeInfo *mimeinfo,
   2548 		gboolean null_terminate)
   2549 {
   2550 	FILE *infp;
   2551 	gchar *data;
   2552 	glong length, readlength;
   2553 
   2554 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
   2555 
   2556 	if (mimeinfo->encoding_type != ENC_BINARY &&
   2557 			!procmime_decode_content(mimeinfo))
   2558 		return NULL;
   2559 
   2560 	if (mimeinfo->content == MIMECONTENT_MEM)
   2561 		return g_strdup(mimeinfo->data.mem);
   2562 
   2563 	if ((infp = g_fopen(mimeinfo->data.filename, "rb")) == NULL) {
   2564 		FILE_OP_ERROR(mimeinfo->data.filename, "g_fopen");
   2565 		return NULL;
   2566 	}
   2567 
   2568 	if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
   2569 		FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
   2570 		fclose(infp);
   2571 		return NULL;
   2572 	}
   2573 
   2574 	length = mimeinfo->length;
   2575 
   2576 	data = g_malloc(null_terminate ? length + 1 : length);
   2577 	if (data == NULL) {
   2578 		g_warning("could not allocate %ld bytes for procmime_get_part_as_string",
   2579 				(null_terminate ? length + 1 : length));
   2580 		fclose(infp);
   2581 		return NULL;
   2582 	}
   2583 
   2584 	readlength = fread(data, length, 1, infp);
   2585 	if (readlength <= 0) {
   2586 		FILE_OP_ERROR(mimeinfo->data.filename, "fread");
   2587 		g_free(data);
   2588 		fclose(infp);
   2589 		return NULL;
   2590 	}
   2591 
   2592 	fclose(infp);
   2593 
   2594 	if (null_terminate)
   2595 		data[length] = '\0';
   2596 
   2597 	return data;
   2598 }
   2599 
   2600 /* Returns an open GInputStream. The caller should just
   2601  * read mimeinfo->length bytes from it and then release it. */
   2602 GInputStream *procmime_get_part_as_inputstream(MimeInfo *mimeinfo)
   2603 {
   2604 	cm_return_val_if_fail(mimeinfo != NULL, NULL);
   2605 
   2606 	if (mimeinfo->encoding_type != ENC_BINARY &&
   2607 			!procmime_decode_content(mimeinfo)) {
   2608 		g_warning("could not decode part");
   2609 		return NULL;
   2610 	}
   2611 	if (mimeinfo->content == MIMECONTENT_MEM) {
   2612 		/* NULL for destroy func, since we're not copying
   2613 		 * the data for the stream. */
   2614 		return g_memory_input_stream_new_from_data(
   2615 				mimeinfo->data.mem,
   2616 				(gssize)mimeinfo->length, NULL);
   2617 	} else {
   2618 		return g_memory_input_stream_new_from_data(
   2619 				procmime_get_part_as_string(mimeinfo, FALSE),
   2620 				mimeinfo->length, g_free);
   2621 	}
   2622 }
   2623 
   2624 GdkPixbuf *procmime_get_part_as_pixbuf(MimeInfo *mimeinfo, GError **error)
   2625 {
   2626 	GdkPixbuf *pixbuf;
   2627 	GInputStream *stream;
   2628 
   2629 	if (error)
   2630 		*error = NULL;
   2631 
   2632 	stream = procmime_get_part_as_inputstream(mimeinfo);
   2633 	if (stream == NULL) {
   2634 		if (error)
   2635 			*error = g_error_new_literal(G_FILE_ERROR, -1, _("Could not decode part"));
   2636 		return NULL;
   2637 	}
   2638 
   2639 	pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, error);
   2640 	g_object_unref(stream);
   2641 
   2642 	if (error && *error != NULL)
   2643 		return NULL;
   2644 
   2645 	return pixbuf;
   2646 }
   2647