messageExport(message *m, const char *dir, void *(*create)(void), void (*destroy)(void *), void (*setFilename)(void *, const char *, const char *), void (*addData)(void *, const unsigned char *, size_t), void *(*exportText)(text *, void *, int), void(*setCTX)(void *, cli_ctx *), int destroy_text)
{
	void *ret;
	text *t_line;
	char *filename;
	int i;

	assert(m != NULL);

	if(messageGetBody(m) == NULL)
		return NULL;

	ret = (*create)();

	if(ret == NULL)
		return NULL;

	cli_dbgmsg("messageExport: numberOfEncTypes == %d\n", m->numberOfEncTypes);

	if(m->numberOfEncTypes == 0) {
		/*
		 * Fast copy
		 */
		cli_dbgmsg("messageExport: Entering fast copy mode\n");

#if	0
		filename = messageGetFilename(m);

		if(filename == NULL) {
			cli_dbgmsg("Unencoded attachment sent with no filename\n");
			messageAddArgument(m, "name=attachment");
		} else if((strcmp(filename, "textportion") != 0) && (strcmp(filename, "mixedtextportion") != 0))
			/*
			 * Some virus attachments don't say how they've
			 * been encoded. We assume base64
			 */
			messageSetEncoding(m, "base64");
#else
		filename = (char *)messageFindArgument(m, "filename");
		if(filename == NULL) {
			filename = (char *)messageFindArgument(m, "name");

			if(filename == NULL) {
				cli_dbgmsg("Unencoded attachment sent with no filename\n");
				messageAddArgument(m, "name=attachment");
			} else
				/*
				 * Some virus attachments don't say how they've
				 * been encoded. We assume base64.
				 * RFC says encoding should be 7-bit.
				 */
				messageSetEncoding(m, "7-bit");
		}
#endif

		(*setFilename)(ret, dir, (filename && *filename) ? filename : "attachment");

		if(filename)
			free((char *)filename);

		if(m->numberOfEncTypes == 0)
			return exportText(messageGetBody(m), ret, destroy_text);
	}

	if(setCTX && m->ctx)
		(*setCTX)(ret, m->ctx);

	for(i = 0; i < m->numberOfEncTypes; i++) {
		encoding_type enctype = m->encodingTypes[i];
		size_t size;

		if(i > 0) {
			void *newret;

			newret = (*create)();
			if(newret == NULL) {
				cli_dbgmsg("Not all decoding algorithms were run\n");
				return ret;
			}
			(*destroy)(ret);
			ret = newret;
		}
		cli_dbgmsg("messageExport: enctype %d is %d\n", i, (int)enctype);
		/*
		 * Find the filename to decode
		 */
		if(((enctype == YENCODE) || (i == 0)) && yEncBegin(m)) {
			const char *f;

			/*
			 * TODO: handle multipart yEnc encoded files
			 */
			t_line = yEncBegin(m);
			f = lineGetData(t_line->t_line);

			if((filename = strstr(f, " name=")) != NULL) {
				filename = cli_strdup(&filename[6]);
				if(filename) {
					cli_chomp(filename);
					strstrip(filename);
					cli_dbgmsg("Set yEnc filename to \"%s\"\n", filename);
				}
			}

			(*setFilename)(ret, dir, (filename && *filename) ? filename : "attachment");
			if(filename) {
				free((char *)filename);
				filename = NULL;
			}
			t_line = t_line->t_next;
			enctype = YENCODE;
			m->yenc = NULL;
		} else {
			if(enctype == UUENCODE) {
				/*
				 * The body will have been stripped out by the
				 * fast track visa system. Treat as plain/text,
				 * which means we'll still scan for funnies
				 * outside of the uuencoded portion.
				 */
				cli_dbgmsg("messageExport: treat uuencode as text/plain\n");
				enctype = m->encodingTypes[i] = NOENCODING;
			}
			filename = messageGetFilename(m);

			if(filename == NULL) {
				cli_dbgmsg("Attachment sent with no filename\n");
				messageAddArgument(m, "name=attachment");
			} else if(enctype == NOENCODING)
				/*
				 * Some virus attachments don't say how
				 * they've been encoded. We assume
				 * base64.
				 *
				 * FIXME: don't do this if it's a fall
				 * through from uuencode
				 */
				messageSetEncoding(m, "base64");

			(*setFilename)(ret, dir, (filename && *filename) ? filename : "attachment");

			t_line = messageGetBody(m);
		}

		if(filename)
			free((char *)filename);

		/*
		 * t_line should now point to the first (encoded) line of the
		 * message
		 */
		if(t_line == NULL) {
			cli_dbgmsg("Empty attachment not saved\n");
			(*destroy)(ret);
			return NULL;
		}

		if(enctype == NOENCODING) {
			/*
			 * Fast copy
			 */
			if(i == m->numberOfEncTypes - 1) {
				/* last one */
				(void)exportText(t_line, ret, destroy_text);
				break;
			}
			(void)exportText(t_line, ret, 0);
			continue;
		}

		size = 0;
		do {
			unsigned char smallbuf[1024];
			unsigned char *uptr, *data;
			const char *line = lineGetData(t_line->t_line);
			unsigned char *bigbuf;
			size_t datasize;

			if(enctype == YENCODE) {
				if(line == NULL)
					continue;
				if(strncmp(line, "=yend ", 6) == 0)
					break;
			}

			/*
			 * Add two bytes for '\n' and '\0'
			 */
			datasize = (line) ? strlen(line) + 2 : 0;

			if(datasize >= sizeof(smallbuf))
				data = bigbuf = (unsigned char *)cli_malloc(datasize);
			else {
				bigbuf = NULL;
				data = smallbuf;
				datasize = sizeof(smallbuf);
			}

			uptr = decodeLine(m, enctype, line, data, datasize);
			if(uptr == NULL) {
				if(data == bigbuf)
					free(data);
				break;
			}

			if(uptr != data) {
				assert((size_t)(uptr - data) < datasize);
				(*addData)(ret, data, (size_t)(uptr - data));
				size += (size_t)(uptr - data);
			}

			if(data == bigbuf)
				free(data);

			/*
			 * According to RFC2045, '=' is used to pad out
			 * the last byte and should be used as evidence
			 * of the end of the data. Some mail clients
			 * annoyingly then put plain text after the '='
			 * byte and viruses exploit this bug. Sigh
			 */
			/*if(enctype == BASE64)
				if(strchr(line, '='))
					break;*/
			if(line && destroy_text && (i == m->numberOfEncTypes - 1)) {
				lineUnlink(t_line->t_line);
				t_line->t_line = NULL;
			}
		} while((t_line = t_line->t_next) != NULL);

		cli_dbgmsg("Exported %lu bytes using enctype %d\n",
			(unsigned long)size, (int)enctype);

		/* Verify we have nothing left to flush out */
		if(m->base64chars) {
			unsigned char data[4];
			unsigned char *ptr;

			ptr = base64Flush(m, data);
			if(ptr)
				(*addData)(ret, data, (size_t)(ptr - data));
		}
	}

	return ret;
}