1 
2 extern "C" {
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <ctype.h>
8 #include <unistd.h>
9 #include <strings.h>
10 #include <errno.h>
11 #include <limits.h>
12 #include <iconv.h>
13 }
14 
15 #include "mime.h"
16 #include "mimetype.h"
17 #include "my_regex.h"
18 #include "tempfile.h"
19 #include "base64.h"
20 
21 #define SILLY_SUN_STUFF			/* Handle Sun's attachment formats */
22 
23 /* MIME default values */
24 #define DEFAULT_VERSION		"1.0"
25 #define DEFAULT_CONTENT		"text/plain"
26 #define DEFAULT_ENCODING	"7bit"
27 #define DEFAULT_CHARSET		"UTF-8"
28 
29 /* Strip HTML from a body of text */
30 static int StripHTML(const char *filename, char **outfile);
31 
32 /* Check if a line is a MIME boundary */
33 static int IsBoundary(char *line, char *boundary, int boundlen);
34 
35 /* List of MIME encodings and stream handlers */
36 struct encoding_handler {
37 	char *type;			/* Case insensitive */
38 	int (*encode)(FILE *input, IObottle *output);
39 	int (*decode)(IObottle *, iconv_t, char **, int *, char **);
40 };
41 
42 int No_Encode(FILE *input, IObottle *output);
43 int QP_Encode(FILE *input, IObottle *output);
44 int Base64_Encode(FILE *input, IObottle *output);
45 
46 int No_Decode(IObottle *body,iconv_t iconv_ctx,char *endstrings[],int endlens[], char **outfile);
47 int QP_Decode(IObottle *body,iconv_t iconv_ctx,char *endstrings[],int endlens[], char **outfile);
48 int Base64_Decode(IObottle *body, iconv_t iconv_ctx,char *endstrings[], int endlens[], char **outfile);
49 
50 /* List of MIME encodings and handlers for them */
51 static struct encoding_handler encodings[] =
52 {
53 	{ "7bit",	No_Encode, No_Decode },
54 	{ "8bit",	No_Encode, No_Decode },
55 	{ "binary",	No_Encode, No_Decode },
56 	{ "quoted-printable", QP_Encode, QP_Decode },
57 	{ "base64",	Base64_Encode, Base64_Decode },
58 	{ NULL,		No_Encode, No_Decode }
59 };
60 
GetContentAttribute(const char * content,const char * attribute,char * output,unsigned int length)61 static bool GetContentAttribute(const char *content, const char *attribute, char *output, unsigned int length)
62 {
63 	const char *ptr;
64 	unsigned int i;
65 	int matchlen = strlen(attribute);
66 
67 	ptr = content;
68 	while ( (ptr=strchr(ptr, ';')) != NULL ) {
69 		++ptr;
70 		while ( isspace(*ptr) ) ++ptr;
71 		if ( strncasecmp(ptr, attribute, matchlen) == 0 )
72 			break;
73 	}
74 	if ( ptr == NULL ) {
75 		return false;
76 	}
77 
78 	ptr += matchlen;
79 	for ( i = 0; i < length-1; ++i ) {
80 		if ( *ptr == ';' || *ptr == ' ' || *ptr == '\0' ) {
81 			break;
82 		}
83 		output[i] = *ptr++;
84 	}
85 	output[i] = '\0';
86 
87 	return true;
88 }
89 
90 /* Converts text between character sets */
ConvertCharset(iconv_t iconv_ctx,char * ibuf,size_t ibytesleft,char * obuf,size_t obytesleft)91 static size_t ConvertCharset(iconv_t iconv_ctx,
92 	char *ibuf, size_t ibytesleft,
93 	char *obuf, size_t obytesleft)
94 {
95 	static char temp[BUFSIZ*2];
96 	static size_t templeft;
97 
98 	/* Should we reset our conversion state? */
99 	if ( ibuf == NULL ) {
100 		templeft = 0;
101 		return 0;
102 	}
103 
104 	/* Use any data left over from the pervious pass */
105 	if ( templeft ) {
106 		memcpy(&temp[templeft], ibuf, ibytesleft);
107 		ibuf = temp;
108 		ibytesleft += templeft;
109 		templeft = 0;
110 	}
111 	size_t available = obytesleft;
112 	while ( ibytesleft > 0 ) {
113 		iconv(iconv_ctx, &ibuf, &ibytesleft, &obuf, &obytesleft);
114 
115 		if ( ibytesleft > 0 ) {
116 			if ( errno == EILSEQ ) {
117 				/* Stuff this one and keep trying */
118 				*obuf = '?';
119 				++ibuf;
120 				--ibytesleft;
121 				++obuf;
122 				--obytesleft;
123 				iconv(iconv_ctx, NULL, NULL, NULL, NULL);
124 				continue;
125 			}
126 			memcpy(temp, ibuf, ibytesleft);
127 			templeft = ibytesleft;
128 			break;
129 		}
130 	}
131 	return(available-obytesleft);
132 }
133 
134 /* Decodes lines based on RFC 2047 */
DecodeAtom(const char * & atom,char * & output,const char * native_charset)135 static void DecodeAtom(const char *&atom, char *&output, const char *native_charset)
136 {
137 	char charset[80];
138 	char encoding;
139 	char *spot;
140 	iconv_t iconv_ctx = (iconv_t)-1;
141 
142 	/* Skip =? */
143 	atom += 2;
144 
145 	/* Copy character set */
146 	spot = charset;
147 	while ( *atom && *atom != '?' ) {
148 		*spot++ = *atom++;
149 	}
150 	if ( *atom ) {
151 		++atom;
152 	}
153 	*spot = '\0';
154 
155 	/* Encoding: 'Q' = quoted-printable, 'B' = base64 */
156 	encoding = *atom;
157 	while ( *atom && *atom != '?' ) {
158 		++atom;
159 	}
160 	if ( *atom ) {
161 		++atom;
162 	}
163 
164 	/* Go, and translate */
165 	spot = output;
166 	if ( encoding == 'Q' || encoding == 'q' ) {
167 		while ( *atom && (*atom != '?') ) {
168 			if ( *atom == '_' ) {
169 				*spot = ' ';
170 				++spot;
171 				++atom;
172 			} else if ( *atom == '=' ) {
173 				/* Make sure no buffer overflow */
174 				++atom;
175 				if ( ! *atom || ! *(atom+1) )
176 					continue;
177 
178 				/* Convert from hexidecimal */
179 				*spot = '\0';
180 				if ( isdigit(*atom) )
181 					*spot |= (*atom-'0');
182 				else
183 					*spot |= (toupper(*atom)-'A'+10);
184 				++atom;
185 				*spot <<= 4;
186 				if ( isdigit(*atom) )
187 					*spot |= (*atom-'0');
188 				else
189 					*spot |= (toupper(*atom)-'A'+10);
190 				++spot;
191 				++atom;
192 			} else
193 				*(spot++) = *(atom++);
194 		}
195 	} else if ( encoding == 'B' || encoding == 'b' ) {
196 		unsigned int olen;
197 		const char *from = atom;
198 		while ( *atom && (*atom != '?') ) {
199 			++atom;
200 		}
201 		olen = base64_decode(from, (atom - from), spot, ~0);
202 		spot += olen;
203 	} else {
204 		while ( *atom && (*atom != '?') ) {
205 			*(spot++) = *(atom++);
206 		}
207 	}
208 	/* Skip ?= */
209 	if ( *atom )
210 		++atom;
211 	if ( *atom == '=' )
212 		++atom;
213 
214 	if ( strcasecmp(charset, native_charset) != 0 ) {
215 		iconv_ctx = iconv_open(native_charset, charset);
216 	}
217 	if ( iconv_ctx != (iconv_t)-1 ) {
218 		char temp[BUFSIZ];
219 		size_t length = ConvertCharset(iconv_ctx,
220 					output, (spot - output),
221 					temp, sizeof(temp));
222 		memcpy(output, temp, length);
223 		spot = (output+length);
224 		iconv_close(iconv_ctx);
225 	}
226 	output = spot;
227 }
228 char *
DecodeLine(const char * line)229 MIME_body::DecodeLine(const char *line)
230 {
231 	char *data, *ptr;
232 	data = new char[strlen(line)+1];
233 
234 	ptr = data;
235 	while ( *line ) {
236 		if ( line[0] == '=' && line[1] == '?' ) {
237 			do {
238 				DecodeAtom(line, ptr, mime_charset);
239 
240 				/* Look ahead to see if there is another atom */
241 				const char *spot;
242 				for ( spot = line; isspace(*spot); ++spot )
243 					;
244 				if ( spot[0] == '=' && spot[1] == '?' )
245 					line = spot;
246 
247 			} while ( line[0] == '=' && line[1] == '?' );
248 
249 			continue;
250 		}
251 		*ptr++ = *line++;
252 	}
253 	*ptr = '\0';
254 
255 	return(data);
256 }
257 
258 /* Allow exclusion of particular MIME types  (this parser sucks..) */
259 char *MIME_body::mime_charset = DEFAULT_CHARSET;
260 void
MIME_Charset(char * charset)261 MIME_body::MIME_Charset(char *charset)
262 {
263 	if ( charset ) {
264 		mime_charset = charset;
265 	} else {
266 		mime_charset = DEFAULT_CHARSET;
267 	}
268 }
269 
270 /* Allow exclusion of particular MIME types  (this parser sucks..) */
271 char **MIME_body::mime_ignore = NULL;
272 void
MIME_Ignore(char * ignorelist)273 MIME_body::MIME_Ignore(char *ignorelist)
274 {
275 	char *ptr, *word;
276 	int   i,   nwords;
277 
278 	/* Clean out existing list */
279 	if ( mime_ignore ) {
280 		for ( i=0; mime_ignore[i]; ++i )
281 			delete[] mime_ignore[i];
282 		delete[] mime_ignore;
283 	}
284 	mime_ignore = NULL;
285 
286 	/* Find out how long the new list is */
287 	nwords = 0;
288 	for ( ptr=ignorelist; ptr; ) {
289 		while ( *ptr && isspace(*ptr) ) ++ptr;
290 		if ( ! *ptr )
291 			break;
292 		while ( *ptr && !isspace(*ptr) ) ++ptr;
293 		++nwords;
294 	}
295 	/* We're done if we have an empty list */
296 	if ( nwords == 0 )
297 		return;
298 
299 	mime_ignore = new char *[nwords+1];
300 	for ( i=0, ptr=ignorelist; i<nwords; ++i ) {
301 		int wordlen;
302 		while ( *ptr && isspace(*ptr) ) ++ptr;
303 		word = ptr;
304 		while ( *ptr && !isspace(*ptr) ) ++ptr;
305 		wordlen = (ptr-word);
306 		mime_ignore[i] = new char[wordlen+1];
307 		memcpy(mime_ignore[i], word, wordlen);
308 		if ( mime_ignore[i][wordlen-1] == ';' ) {
309 			mime_ignore[i][wordlen-1] = 0;
310 		} else {
311 			mime_ignore[i][wordlen] = 0;
312 		}
313 	}
314 	mime_ignore[i] = NULL;
315 }
316 
317 
318 /* The MIME_body class constructor */
MIME_body(IObottle * RawFile,char * endings[],MIME_body * lineage)319 MIME_body:: MIME_body(IObottle *RawFile, char *endings[],
320 						MIME_body *lineage)
321 {
322 	MD5_CTX md5_ctx;
323 	int i;
324 	char *ptr;
325 	char line[BUFSIZ];
326 
327 	/* Get the file. :) */
328 	rawfile = RawFile;
329 	parent  = lineage;
330 	decoded = NULL;
331 	bodyref = 0;
332 	hstart  = rawfile->tellg();
333 
334 	/* Copy the boundary string pointers */
335 	for ( numends = 0; endings[numends]; ++numends );
336 	endlens = new int[numends];
337 	endstrings = new char *[numends+1];
338 	for ( i = 0; endings[i]; ++i ) {
339 		endstrings[i] = endings[i];
340 		endlens[i] = strlen(endings[i]);
341 	}
342 	endstrings[i] = NULL;
343 
344 	/* Read in the header until the first newline */
345 	char lastfield[1024], *lastdata=NULL;
346 	do {
347 		/* Note, we don't check for boundary lines here.
348 		   It's mostly because mail messages will have a boundary
349 		   line as the first line, while MIME messages will not.
350 		   If I don't check for boundaries in the header, I avoid
351 		   the problem, but make it possible to mis-read a header
352 		   that isn't followed by a newline.  (a problem?)
353 		*/
354 		rawfile->readline(line, sizeof(line));
355 
356 		/* This allows us to put comments in a "header" :-) */
357 		if ( line[0] == '#' )
358 			continue;
359 
360 		/* If the header line starts with whitespace, it continues
361 		   the last field.
362 		*/
363 		if ( isspace(line[0]) && lastdata ) {
364 			/* Remove the last field */
365 			fieldtab.Remove(lastfield);
366 
367 			/* Create new appended field data */
368 			for ( ptr=line; isspace(*ptr); ++ptr );
369 			char *data = new char
370 					[strlen(lastdata)+1+strlen(ptr)+1];
371 			sprintf(data, "%s %s", lastdata, ptr);
372 			delete[] lastdata;
373 
374 			/* Insert it into the hash list */
375 			fieldtab.Add(lastfield, data);
376 			lastdata = data;
377 		} else {
378 			/* If the header line contains ": " it is hashable */
379 			lastdata = NULL;
380 			if ( ((ptr=strstr(line, ":")) != NULL)
381 					&& isspace(*(ptr+1)) ) {
382 				char *data;
383 				*ptr = '\0';
384 				if ( GetField(line) == NULL ) {
385 					for ( ptr += 2; isspace(*ptr); ++ptr );
386 					/* Translate inline QP characters */
387 					data = DecodeLine(ptr);
388 					fieldtab.Add(line, data);
389 					strcpy(lastfield, line);
390 					lastdata = data;
391 				}
392 			}
393 		}
394 	} while ( ! rawfile->eof() && (line[0] != '\0') );
395 
396 	/* We should check MIME version, but... :-)
397 	   We're an incomplete implementation anyway, and future versions
398 	   of MIME will probably be backwards compatible.
399 	 */
400 	if ( (version=GetField("Mime-Version")) == NULL )
401 		version = DEFAULT_VERSION;
402 
403 	/* Set the content */
404 	if ( (content=GetField("Content-Type")) != NULL ) {
405 #ifdef SILLY_SUN_STUFF
406 		/* Handle special attachment types */
407 		if ( strncasecmp(content, "X-sun-attachment",
408 					strlen("X-sun-attachment")) == 0 ) {
409 			content = "multipart/mixed; boundary=\"--------\"";
410 		}
411 #endif /* SILLY_SUN_STUFF */
412 	} else
413 		content = DEFAULT_CONTENT;
414 
415 	/* Set the encoding (support MIME and Sun protocol) */
416 	if ( (encoding=GetField("Content-Transfer-Encoding")) == NULL ) {
417 #ifdef SILLY_SUN_STUFF
418 		/* Handle special attachment types */
419 		if ( (encoding=GetField("X-Sun-Encoding-Info")) != NULL ) {
420 			if ( strcasecmp(encoding, "base64") == 0 ) {
421 				/* Set an application content type */
422 				content = "application/X-sun-unknown";
423 			}
424 		} else
425 #endif /* SILLY_SUN_STUFF */
426 			encoding = DEFAULT_ENCODING;
427 	}
428 
429 	/* Try to get a name for this body */
430 	if ((ptr=(char *)match_nocase((char *)content, "Name=", &i)) != NULL) {
431 		/* Pull out an unquoted name */
432 		ptr += i;
433 		GetWord(ptr, &name);
434 	} else
435 	if ( (ptr=(char *)match_nocase((char *)GetField("Content-Disposition"),
436 			"FileName=", &i)) != NULL ) {
437 		/* Pull out an unquoted name */
438 		ptr += i;
439 		GetWord(ptr, &name);
440 	} else
441 #ifdef SILLY_SUN_STUFF
442 	if ( (ptr=(char *)GetField("X-Sun-Data-Type")) != NULL ) {
443 		name = new char[strlen(ptr)+1];
444 		strcpy(name, ptr);
445 	} else
446 #endif /* SILLY_SUN_STUFF */
447 		name = NULL;
448 
449 	/* Suck up the body, handling multipart messages */
450 	bstart = rawfile->tellg();
451 	MD5Init(&md5_ctx);
452 	multipart = NULL;
453 	if ( strncasecmp(content, "multipart", strlen("multipart")) == 0 ) {
454 		(void) ParseMulti(&md5_ctx);
455 
456 		/* For now, delete complex parts we can't view */
457 		if ( multipart && (strncasecmp(content, "multipart/alternative",
458 					strlen("multipart/alternative")) == 0)){
459 			MIME_body  **dataptr;
460 			multipart->InitIterator();
461 			multipart->Iterate();		/* Keep simple part */
462 			while ( (dataptr=multipart->Iterate()) ) {
463 				MIME_body *oldpart = *dataptr;
464 				multipart->Remove(dataptr);
465 				delete oldpart;		/* Delete other part */
466 			}
467 		}
468 
469 		/* Delete any MIME parts that we have been told to ignore */
470 		if ( multipart && mime_ignore ) {
471 			MIME_body  **dataptr;
472 			multipart->InitIterator();
473 			while ( (dataptr=multipart->Iterate()) ) {
474 				for ( i=0; mime_ignore[i]; ++i ) {
475 					if ( strncasecmp((*dataptr)->Content(), mime_ignore[i], strlen(mime_ignore[i])) == 0 ) {
476 						MIME_body *oldpart = *dataptr;
477 						multipart->Remove(dataptr);
478 						delete oldpart;
479 						break;
480 					}
481 				}
482 			}
483 		}
484 
485 		/* Multipart bodies that have only one subpart are, in effect,
486 		   proxies for us, and have our parents.
487 		*/
488 		if ( multipart && (multipart->Size() == 1) ) {
489 			MIME_body  **dataptr;
490 
491 			multipart->InitIterator();
492 			dataptr = multipart->Iterate();
493 			(*dataptr)->parent = parent;
494 		}
495 	} else {
496 		/* Move the file pointer to the end of this body */
497 		for ( ; ; ) {
498 			int n, len;
499 			long here;
500 
501 			/* Read in the next line, checking for boundary */
502 			here = rawfile->tellg();
503 			len = rawfile->readline(line, sizeof(line));
504 			if ( rawfile->eof() )
505 				break;
506 			for ( n=0; endstrings[n]; ++n ) {
507 				/* If we have a match, break out */
508 				if (IsBoundary(line, endstrings[n], endlens[n]))
509 					break;
510 			}
511 			if ( endstrings[n] ) {
512 				rawfile->seekg(here);
513 				break;
514 			}
515 			MD5Update(&md5_ctx, (unsigned char *)line, len);
516 		}
517 	}
518 	MD5Final(md5sum, &md5_ctx);
519 
520 	/* Tidy up */
521 	addedparts = NULL;
522 	if ( multipart )
523 		currentpart = 1;
524 	else
525 		currentpart = 0;
526 	return;
527 }
528 
~MIME_body()529 MIME_body:: ~MIME_body()
530 {
531 	/* Free up memory used by header data strucures */
532 	char **dataptr;
533 	fieldtab.InitIterator();
534 	while ( (dataptr=fieldtab.Iterate()) )
535 		delete[] *dataptr;
536 
537 	/* Remove any temp file */
538 	while ( FreeFile() == 0 ) {
539 		/* Force a close */;
540 	}
541 
542 	/* Free up any sub-part MIME bodies */
543 	if ( multipart ) {
544 		MIME_body  **dataptr;
545 		multipart->InitIterator();
546 		while ( (dataptr=multipart->Iterate()) )
547 			delete *dataptr;
548 		delete multipart;
549 	}
550 	if ( addedparts ) {
551 		IObottle  **dataptr;
552 		addedparts->InitIterator();
553 		while ( (dataptr=addedparts->Iterate()) )
554 			delete *dataptr;
555 		delete addedparts;
556 	}
557 	if ( name )
558 		delete[] name;
559 
560 	/* Finally, clean up random memory chunks */
561 	delete[] endstrings;
562 	delete[] endlens;
563 }
564 
565 /* Quick utility routine for getting a (possibly quoted) word from a string */
566 void
GetWord(const char * src,char ** dst)567 MIME_body:: GetWord(const char *src, char **dst)
568 {
569 	const char *src_head;
570 	int   wordlen;
571 
572 	src_head = src;
573 	if ( *src == '"' ) {
574 		++src_head;
575 		++src;
576 		while ( *src && (*src != '"') )
577 			++src;
578 	} else {
579 		while ( *src && ! isspace(*src) && (*src != ';') )
580 			++src;
581 	}
582 	wordlen = (src-src_head);
583 	*dst = new char[wordlen+1];
584 	strncpy(*dst, src_head, wordlen);
585 	(*dst)[wordlen] = '\0';
586 }
587 
588 /* Create a sub-menu of body parts ;-) */
589 int
ParseMulti(MD5_CTX * md5ctx)590 MIME_body:: ParseMulti(MD5_CTX *md5ctx)
591 {
592 	int    n, seplen;
593 	char  *newcontent;
594 	char **newends;
595 	int   *newendlens;
596 	char   line[BUFSIZ];
597 	long   here;
598 
599 	/* Copy the content string so we can muck it up :) */
600 	newcontent = new char[strlen(content)+1];
601 	strcpy(newcontent, content);
602 
603 	/* Try to find the boundary */
604 	char *mstr, *sepstr;
605 	int   mlen;
606 	if ( (sepstr=(char *)match_nocase(newcontent, "Boundary=", &mlen)) == NULL ) {
607 		delete[] newcontent;
608 		return(-1);
609 	}
610 	sepstr += mlen;
611 	if ( *sepstr == '"' ) {
612 		for ( mstr=(++sepstr); *mstr && (*mstr != '"'); ++mstr );
613 	} else {
614 		for ( mstr=sepstr;
615 			*mstr && (*mstr != ';') && !isspace(*mstr); ++mstr );
616 	}
617 	*mstr = '\0';
618 	/* MIME boundaries are "--boundary" */
619 	*(--sepstr) = '-';
620 	*(--sepstr) = '-';
621 
622 	/* Add in the boundary as a new ending */
623 	newends = new char *[numends+1+1];
624 	newendlens = new int [numends+1];
625 	for ( n=0; endstrings[n]; ++n ) {
626 		newends[n] = endstrings[n];
627 		newendlens[n] = endlens[n];
628 	}
629 	newends[n] = sepstr;
630 	seplen = strlen(sepstr);
631 	newendlens[n] = seplen;
632 	newends[n+1] = NULL;
633 
634 	/* Suck up to the first boundary -- no pun intended. :) */
635 	here = rawfile->tellg();
636 	for ( ; ; ) {
637 		int len;
638 
639 		here = rawfile->tellg();
640 
641 		/* Read in the next line, checking for termination boundary */
642 		len = rawfile->readline(line, sizeof(line));
643 		if ( rawfile->eof() )
644 			break;
645 
646 		for ( n=0; newends[n]; ++n ) {
647 			if ( IsBoundary(line, newends[n], newendlens[n]) )
648 				break;
649 		}
650 		if ( newends[n] )
651 			break;
652 
653 		MD5Update(md5ctx, (unsigned char *)line, len);
654 	}
655 	if ( newends[n] == NULL ) { /* EOF? */
656 		delete[] newcontent;
657 		delete[] newends;
658 		delete[] newendlens;
659 		return(-1);
660 	}
661 
662 	/* Make sure we didn't hit an upper boundary */
663 	if ( strncmp(newends[n], sepstr, seplen) != 0 ) {
664 		rawfile->seekg(here);
665 		delete[] newcontent;
666 		delete[] newends;
667 		delete[] newendlens;
668 		return(-1);
669 	}
670 
671 	/* Warning: Memory Leak!
672 	   At this point we never free 'newcontent', since it stays in use
673 	   by any child MIME bodies we create here.
674 	*/
675 
676 	multipart = new List<MIME_body *>;
677 	do {
678 		/* Elegant, isn't it? */
679 		MIME_body *newbody = new MIME_body(rawfile, newends, this);
680 		MD5Update(md5ctx, newbody->md5sum, 16);
681 		multipart->Add(newbody);
682 
683 		/* Check the next boundary line */
684 		here = rawfile->tellg();
685 		rawfile->readline(line, sizeof(line));
686 
687 		/* Anything other than our separator, we quit here */
688 		if ( strncmp(line, sepstr, seplen) != 0 )
689 			break;
690 
691 		/* If we find "boundary--", then we suck up input and quit */
692 		if ( (line[seplen] == '-') && (line[seplen+1] == '-') ) {
693 			for ( ; ; ) {
694 				here = rawfile->tellg();
695 
696 				rawfile->readline(line, sizeof(line));
697 				if ( rawfile->eof() )
698 					break;
699 
700 				for ( n=0; endstrings[n]; ++n ) {
701 					if ( IsBoundary(line, endstrings[n],
702 								endlens[n]) )
703 						break;
704 				}
705 				if ( endstrings[n] )
706 					break;
707 			}
708 			break;
709 		}
710 	} while ( ! rawfile->eof() );
711 
712 	/* That's it!  We're done! :) */
713 	if ( ! rawfile->eof() )
714 		rawfile->seekg(here);
715 	delete[] newends;
716 	delete[] newendlens;
717 	return(0);
718 }
719 
720 int
Open(void)721 MIME_body:: Open(void)
722 {
723 	int i, status;
724 	iconv_t iconv_ctx = (iconv_t)-1;
725 
726 	/* Save the body to a file, with appropriate translation */
727 	rawfile->seekg(bstart);
728 
729 	if ( strncmp(Content(), "text", strlen("text")) == 0 ) {
730 		char charset[1024];
731 		if ( GetContentAttribute(Content(), "charset=", charset, sizeof(charset)) &&
732 		     (strcasecmp(charset, mime_charset) != 0) ) {
733 			iconv_ctx = iconv_open(mime_charset, charset);
734 		}
735 	}
736 	for ( i=0; encodings[i].type; ++i ) {
737 		if ( strcasecmp(encodings[i].type, encoding) == 0 )
738 			break;
739 	}
740 	status = encodings[i].decode(rawfile, iconv_ctx, endstrings, endlens, &decoded);
741 	if ( iconv_ctx != (iconv_t)-1 ) {
742 		iconv_close(iconv_ctx);
743 	}
744 	if ( status < 0 ) {
745 		return status;
746 	}
747 	if ( IsHTML() ) {
748 		char *filename = decoded;
749 		if ( StripHTML(filename, &decoded) == 0 ) {
750 			unlink(filename);
751 			delete[] filename;
752 		}
753 	}
754 	return status;
755 }
756 
757 char *
GetHeader(char ** keyholder)758 MIME_body:: GetHeader(char **keyholder)
759 {
760 	char **value;
761 	if ( keyholder == NULL ) {
762 		fieldtab.InitIterator();
763 		return(NULL);
764 	}
765 	if ( (value=fieldtab.Iterate(keyholder)) == NULL )
766 		return(NULL);
767 	return(*value);
768 }
769 
770 const char *
GetField(const char * key)771 MIME_body:: GetField(const char *key)
772 {
773 	char **dataptr;
774 	if ( (dataptr=fieldtab.Search(key)) )
775 		return(*dataptr);
776 	return(NULL);
777 }
778 
779 void
NewField(const char * name,const char * value)780 MIME_body:: NewField(const char *name, const char *value)
781 {
782 	char *sptr;
783 
784 	/* Remove any existing value */
785 	if ((sptr=(char *)GetField(name))) {
786 		delete[] sptr;
787 		fieldtab.Remove(name);
788 	}
789 	sptr = new char[strlen(value)+1];
790 	strcpy(sptr, value);
791 	fieldtab.Add(name, sptr);
792 }
793 
794 /* Allow searching of MIME bodies, matching within lines  */
795 MIME_body *
Search(const char * pattern)796 MIME_body:: Search(const char *pattern)
797 {
798 	FILE *rawbody;
799 	int   found = 0;
800 
801 	/* First check ourselves */
802 	if ( (strncmp(Content(), "text", strlen("text")) == 0) &&
803 				((rawbody=fopen(File(), "r")) != NULL) ) {
804 		char buffer[BUFSIZ];
805 		int  matchlen;
806 
807 		while ( fgets(buffer, BUFSIZ-1, rawbody) ) {
808 			if ( match(buffer, pattern, &matchlen) ) {
809 				found = 1;
810 				break;
811 			}
812 		}
813 		fclose(rawbody);
814 		FreeFile();
815 	}
816 	if ( found )
817 		return(this);
818 
819 	/* Check our children */
820 	if ( multipart ) {
821 		MIME_body **bodyptr;
822 		MIME_body  *okay;
823 
824 		multipart->InitIterator();
825 		okay = NULL;
826 		while ( ! okay && (bodyptr=multipart->Iterate()) ) {
827 			okay = (*bodyptr)->Search(pattern);
828 		}
829 		return(okay ? okay : (MIME_body *)0);
830 	}
831 	return(NULL);
832 }
833 
834 /* Add a part to the current body.
835    Note, because we call MultipartMe(), we must be rewritten (with a greater
836    length) to a new IO stream with SaveRaw(), or these changes will be lost.
837  */
838 int
AddPart(const char * file,int is_mime)839 MIME_body:: AddPart(const char *file, int is_mime)
840 {
841 	MD5_CTX md5_ctx;
842 	int (*EncodeFile)(FILE *, IObottle *);
843 	FILE *input;
844 	char  line[BUFSIZ];
845 	int   len, blen;
846 	long  here;
847 	const char *ptr;
848 	char *boundary;
849 	char *newcontent, *newencoding;
850 
851 	/* Open the input file */
852 	if ( ((newencoding=MIME_Encoding(file)) == NULL) ||
853 				((input=fopen(file, "r")) == NULL) )
854 		return(-1);
855 
856 	/* Find the encoder we use */
857 	for ( len = 0; encodings[len].type; ++len ) {
858 		if ( strcasecmp(newencoding, encodings[len].type) == 0 )
859 			break;
860 	}
861 	EncodeFile = encodings[len].encode;
862 
863 	/* Make sure we are a multipart message */
864 	if ( MultipartMe(&boundary) < 0 ) {
865 		fclose(input);
866 		return(-1);
867 	}
868 	blen = strlen(boundary);
869 
870 	/* Add the new file, first seek to end of message */
871 	rawfile->seekg(bstart);
872 	do {
873 		here = rawfile->tellg();
874 		len = rawfile->readline(line, BUFSIZ);
875 		if ( (line[0] == '-') && (line[1] == '-') ) {
876 			if ( strncmp(&line[2], boundary, blen) == 0 ) {
877 				if ( (line[2+blen] == '-') &&
878 						(line[2+blen+1] == '-') ) {
879 					break;
880 				}
881 			}
882 		}
883 	} while ( ! rawfile->fail() && ! rawfile->eof() );
884 	rawfile->seekp(here);
885 
886 	/* Add boundary */
887 	rawfile->printf("--%s\n", boundary);
888 	if ( ! is_mime ) {
889 		if ( (newcontent=MIME_Type(file)) == NULL ) {
890 			if ( strcmp(newencoding, "base64") == 0 )
891 				newcontent = DEFAULT_BIN;
892 			else
893 				newcontent = DEFAULT_TXT;
894 		}
895 		/* Skip the full path of the file when naming the content */
896 		if ( (ptr=strrchr(file, '/')) != NULL )
897 			file = ptr+1;
898 		rawfile->printf("Content-Type: %s; name=\"%s\"\n",
899 							newcontent, file);
900 		rawfile->printf("Content-Transfer-Encoding: %s\n", newencoding);
901 		rawfile->printf("\n");
902 	}
903 	if ( (*EncodeFile)(input, rawfile) < 0 ) {
904 		fclose(input);
905 		rawfile->seekg(here);
906 		rawfile->printf("--%s--\n", boundary);
907 		rawfile->truncate(rawfile->tellp());
908 		delete[] boundary;
909 		return(-1);
910 	}
911 	fclose(input);
912 	rawfile->printf("--%s--\n", boundary);
913 	rawfile->truncate(rawfile->tellp());
914 	delete[] boundary;
915 
916 	/* Free up any sub-part MIME bodies */
917 	if ( multipart ) {
918 		MIME_body  **dataptr;
919 		multipart->InitIterator();
920 		while ( (dataptr=multipart->Iterate()) )
921 			delete *dataptr;
922 		delete multipart;
923 		multipart = NULL;
924 	}
925 
926 	/* Create the new MIME body sublist */
927 	rawfile->seekg(bstart);
928 	MD5Init(&md5_ctx);
929 	(void) ParseMulti(&md5_ctx);
930 	MD5Final(md5sum, &md5_ctx);
931 	return(0);
932 }
933 
934 /* NOTE:  After this function runs, we are no longer tied to the main MIME
935           message base.  We are fully contained in a separate temporary file,
936 	  and must be rewritten to a stream via the SaveRaw() function.
937 */
938 int
MultipartMe(char ** boundaryptr)939 MIME_body:: MultipartMe(char **boundaryptr)
940 {
941 	/* Output variables */
942 	int   i;
943 	char temp_name[PATH_MAX];
944 	IObottle *output;
945 
946 	/* MIME variables */
947 	const char *oldcontent = Content();
948 	const char *oldencoding = Encoding();
949 	char *newcontent = "multipart/mixed; boundary=";
950 	char *boundary, *ptr;
951 	int   set_version, set_content, set_encoding;
952 
953 	/* Buffer copy variables */
954 	char  line[BUFSIZ];
955 	int   len, n;
956 	long  oldpos;
957 
958 	/* Don't do anything if we are already multipart */
959 	if ( strncmp(oldcontent, "multipart/mixed", strlen("multipart/mixed"))
960 									== 0 ) {
961 		/* Try to find the boundary */
962 		char *mstr, *sepstr;
963 		int   mlen;
964 
965 		newcontent = new char[strlen(oldcontent)+1];
966 		strcpy(newcontent, oldcontent);
967 		if ( (sepstr=(char *)match_nocase(newcontent, "Boundary=",
968 							&mlen)) == NULL ) {
969 			delete[] newcontent;
970 			return(-1);
971 		}
972 		sepstr += mlen;
973 		if ( *sepstr == '"' )
974 			for (mstr=(++sepstr); *mstr && (*mstr != '"'); ++mstr);
975 		else
976 			for (mstr=sepstr; *mstr && !isspace(*mstr); ++mstr);
977 		*mstr = '\0';
978 
979 		/* Copy it in.. */
980 		*boundaryptr = new char[strlen(sepstr)+1];
981 		strcpy(*boundaryptr, sepstr);
982 		delete[] newcontent;
983 		return(0);
984 	}
985 
986 	/* Open a new file */
987 	FILE *teaser = create_tempfile("w", temp_name);
988 	if ( teaser == NULL ) {
989 		return(-1);
990 	}
991 	fclose(teaser);
992 	output = new IObottle(temp_name);
993 	if ( output->fail() ) {
994 		delete output;
995 		return(-1);
996 	}
997 	/* We are primed and ready. :) */
998 
999 	/* Create the new content type and boundary */
1000 	/* WARNING: Memory leak!
1001 		content[] is never freed, since normally it is read-only.
1002 	*/
1003 	content = new char[strlen(newcontent)+1+32+1+1+1];
1004 	strcpy((char *)content, newcontent);
1005 	strcat((char *)content, "\"");
1006 	boundary = (char *)content+strlen(newcontent)+1;
1007 	for ( i=0, ptr=boundary; i<16; ++i, ptr += 2 )
1008 		sprintf(ptr, "%.2X", md5sum[i]);
1009 	*ptr = '\0';
1010 	*boundaryptr = new char[2*16+1];
1011 	strcpy(*boundaryptr, boundary);
1012 	strcat((char *)content, "\";");
1013 	encoding = "7bit";
1014 
1015 	/* Write out our new header */
1016 	oldpos = rawfile->tellg();
1017 	rawfile->seekg(hstart);
1018 	hstart = 0;
1019 	set_version = set_content = set_encoding = 0;
1020 	do {
1021 		/* Read a header line */
1022 		len = rawfile->readline(line, BUFSIZ);
1023 
1024 		/* Check for header clean up at EOH (here) */
1025 		if ( len == 1 ) {
1026 			if ( ! set_version ) {
1027 				output->printf("MIME-Version: %s\n", version);
1028 			}
1029 			if ( ! set_content ) {
1030 				output->printf("Content-Type: %s\n", content);
1031 			}
1032 			if ( ! set_encoding ) {
1033 				output->printf(
1034 				"Content-Transfer-Encoding: %s\n", encoding);
1035 			}
1036 		}
1037 
1038 		/* Translate a couple of fields, spit out the rest */
1039 		if ( strncasecmp(line, "MIME-Version:",
1040 					strlen("MIME-Version:")) == 0 ) {
1041 			output->printf("MIME-Version: %s\n", version);
1042 			set_version = 1;
1043 		} else
1044 		if ( strncasecmp(line, "Content-Type:",
1045 					strlen("Content-Type:")) == 0 ) {
1046 			output->printf("Content-Type: %s\n", content);
1047 			set_content = 1;
1048 		} else
1049 		if ( strncasecmp(line, "Content-Transfer-Encoding:",
1050 				strlen("Content-Transfer-Encoding:")) == 0 ) {
1051 			output->printf("Content-Transfer-Encoding: %s\n",
1052 								encoding);
1053 			set_encoding = 1;
1054 		} else
1055 			output->writeline(line, len);
1056 	} while ( strlen(line) > 0 );
1057 
1058 	/* Print the intro and the initial body separator */
1059 	bstart = output->tellg();
1060 	output->writeline("\nThis is a multi-part message in MIME format.\n\n");
1061 	output->printf("--%32.32s\n", boundary);
1062 
1063 	/* Print out our existing body */
1064 	output->printf("Content-Type: %s; name=\"Message Text\"\n", oldcontent);
1065 	output->printf("Content-Transfer-Encoding: %s\n", oldencoding);
1066 	output->printf("\n");
1067 	for ( ; ; ) {
1068 		/* Read in the next line, checking for boundary */
1069 		len = rawfile->readline(line, BUFSIZ);
1070 		if ( rawfile->eof() )
1071 			break;
1072 		for ( n=0; endstrings[n]; ++n ) {
1073 			/* If we have a match, break out */
1074 			if (IsBoundary(line, endstrings[n], endlens[n]))
1075 				break;
1076 		}
1077 		if ( endstrings[n] )
1078 			break;
1079 		output->writeline(line, len);
1080 	}
1081 	output->printf("--%32.32s--\n", boundary);
1082 	output->flush();
1083 
1084 	/* That's it! */
1085 	if ( addedparts == NULL )
1086 		addedparts = new List<IObottle *>;
1087 	addedparts->Add(output);
1088 	rawfile = output;
1089 	return(1);
1090 }
1091 
1092 /* Save the body to a file */
1093 int
Save(char * filename,int overwrite)1094 MIME_body:: Save(char *filename, int overwrite)
1095 {
1096 	char *ourfile;
1097 	FILE *input, *output;
1098 	char *mode;
1099 	char  buffer[BUFSIZ];
1100 	unsigned int blen;
1101 	int   retval;
1102 
1103 	if ( ! filename || ! *filename )
1104 		return(0);
1105 
1106 	switch (overwrite) {
1107 		case 0: {
1108 			struct stat sb;
1109 			if ( stat(filename, &sb) == 0 ) {
1110 				errno = EEXIST;
1111 				return(-1);
1112 			}
1113 			mode = "w";
1114 			}
1115 			break;
1116 		case 1:
1117 			mode = "w";
1118 			break;
1119 		case 2:
1120 			mode = "a";
1121 			break;
1122 		default:
1123 			errno = EINVAL;
1124 			return(-1);
1125 	}
1126 
1127 	/* Open the files */
1128 	ourfile = decoded;
1129 	if ( ourfile == NULL ) {	/* Do a temporary open */
1130 		if ( Open() < 0 ) {
1131 			return(-1);
1132 		}
1133 		ourfile = decoded;
1134 		decoded = NULL;
1135 	}
1136 	if ( (output=fopen(filename, mode)) == NULL ) {
1137 		if ( decoded == NULL )
1138 			(void) unlink(ourfile);
1139 		return(-1);
1140 	}
1141 	if ( (input=fopen(ourfile, "r")) == NULL ) {
1142 		fclose(output);
1143 		if ( decoded == NULL )
1144 			(void) unlink(ourfile);
1145 		return(-1);
1146 	}
1147 
1148 	/* Just DO it */
1149 	retval = 0;
1150 	while ( (blen=fread(buffer, 1, BUFSIZ, input)) > 0 ) {
1151 		/* Write okay? */
1152 		if ( fwrite(buffer, 1, blen, output) != blen ) {
1153 			retval = -1;
1154 			break;
1155 		}
1156 	}
1157 	/* Read error? */
1158 	if ( ! feof(input) )
1159 		retval = -1;
1160 
1161 	/* Clean up and exit */
1162 	fclose(input);
1163 	fclose(output);
1164 	if ( decoded == NULL )
1165 		(void) unlink(ourfile);
1166 	return(retval);
1167 }
1168 
StripHTML(const char * filename,char ** outfile)1169 static int StripHTML(const char *filename, char **outfile)
1170 {
1171 	char temp_name[PATH_MAX];
1172 	FILE *rfp, *wfp;
1173 	char ibuffer[BUFSIZ];
1174 	char obuffer[BUFSIZ];
1175 	size_t ilength;
1176 	size_t olength;
1177 	bool in_element = false;
1178 
1179        	rfp = fopen(filename, "r");
1180 	if ( rfp == NULL ) {
1181 		return -1;
1182 	}
1183 	wfp = create_tempfile("w", temp_name);
1184 	if ( wfp == NULL ) {
1185 		fclose(rfp);
1186 		return -1;
1187 	}
1188 	while ( (ilength=fread(ibuffer, 1, sizeof(ibuffer), rfp)) > 0 ) {
1189 		char *output = obuffer;
1190 		for ( size_t i = 0; i < ilength; ++i ) {
1191 			if ( in_element ) {
1192 				if ( ibuffer[i] == '>' ) {
1193 					in_element = false;
1194 				}
1195 			} else if ( ibuffer[i] == '<' ) {
1196 				in_element = true;
1197 			} else if ( ibuffer[i] == '&' ) {
1198 				if ( strncmp(&ibuffer[i], "&nbsp;", 6) == 0 ) {
1199 					*output++ = ' ';
1200 					i += 5;
1201 				} else if ( strncmp(&ibuffer[i], "&amp;", 5) == 0 ) {
1202 					*output++ = '&';
1203 					i += 4;
1204 				} else if ( strncmp(&ibuffer[i], "&lt;", 4) == 0 ) {
1205 					*output++ = '<';
1206 					i += 3;
1207 				} else if ( strncmp(&ibuffer[i], "&gt;", 4) == 0 ) {
1208 					*output++ = '>';
1209 					i += 3;
1210 				} else {
1211 					*output++ = ibuffer[i];
1212 				}
1213 			} else {
1214 				*output++ = ibuffer[i];
1215 			}
1216 		}
1217 		olength = (output - obuffer);
1218 		if ( fwrite(obuffer, 1, olength, wfp) != olength ) {
1219 			fclose(rfp);
1220 			fclose(wfp);
1221 			unlink(temp_name);
1222 			return(-1);
1223 		}
1224 	}
1225 	fclose(rfp);
1226 	fclose(wfp);
1227 
1228 	*outfile = new char[strlen(temp_name)+1];
1229 	strcpy(*outfile, temp_name);
1230 	return(0);
1231 }
1232 
1233 /* Check if a line is a MIME boundary */
IsBoundary(char * line,char * boundary,int boundlen)1234 static int IsBoundary(char *line, char *boundary, int boundlen)
1235 {
1236 	int bodies_ended = 0;
1237 
1238 	/* Check for boundary matching */
1239 	if ( strncmp(line, boundary, boundlen) != 0 )
1240 		return(0);
1241 #ifdef DEBUG
1242 	printf("Boundary Matched: %s\n", line);
1243 #endif
1244 	/* First part of boundary matched, now either it's a normal
1245 	   MIME boundary (terminated by whitespace) or it's a special
1246 	   "no more bodies" boundary (terminated by "--")
1247 	*/
1248 	if ( (line[boundlen] == '-') && (line[boundlen+1] == '-') ) {
1249 		bodies_ended = 1;
1250 		boundlen += 2;
1251 	}
1252 	if ( ! line[boundlen] || isspace(line[boundlen]) )
1253 		return(1+bodies_ended);
1254 	return(0);
1255 }
1256 
1257 /* Okay, the basic structure of a decoder is:
1258 
1259 	Open a temporary file.
1260 	Copy from IObottle to temporary file, performing appropriate decoding.
1261 	Set outfile to NULL on write error and return -1.
1262 	Quit writing at "endstrings" and rewind to the line before.
1263 	Set outfile to name of temporary file and return 0.
1264 */
1265 
No_Decode(IObottle * body,iconv_t iconv_ctx,char * endstrings[],int endlens[],char ** outfile)1266 int No_Decode(IObottle *body, iconv_t iconv_ctx, char *endstrings[], int endlens[], char **outfile)
1267 {
1268 	long here = body->tellg();
1269 	char temp_name[PATH_MAX];
1270 	FILE *output;
1271 	char  line[BUFSIZ], converted_line[BUFSIZ*2], *data;
1272 	int   n, finished;
1273 
1274 	/* Grab a temporary file */
1275 	if ( (output=create_tempfile("w", temp_name)) == NULL )
1276 		return(-1);
1277 
1278 	if ( iconv_ctx != (iconv_t)-1 ) {
1279 		ConvertCharset(iconv_ctx, NULL, 0, NULL, 0);
1280 	}
1281 
1282 	/* Just DO it! :) */
1283 	for ( finished = 0; ! finished; ) {
1284 		here = body->tellg();
1285 
1286 		/* Read in the next line, checking for termination boundary */
1287 		body->readline(line, sizeof(line));
1288 		if ( body->eof() ) {
1289 			finished = 1;
1290 			continue;
1291 		}
1292 		for ( n=0; endstrings[n]; ++n ) {
1293 			if ( IsBoundary(line,endstrings[n],endlens[n]) )
1294 				break;
1295 		}
1296 		if ( endstrings[n] ) {
1297 			finished = 1;
1298 			continue;
1299 		}
1300 
1301 		/* Do any processing here */
1302 		if ( iconv_ctx != (iconv_t)-1 ) {
1303 			size_t len = ConvertCharset(iconv_ctx, line, strlen(line), converted_line, sizeof(converted_line)-1);
1304 			converted_line[len] = '\0';
1305 			data = converted_line;
1306 		} else {
1307 			data = line;
1308 		}
1309 
1310 		/* Write to the temporary file */
1311 		if ( (fputs(data, output) == EOF) ||
1312 		     (fputc('\n', output) == EOF) ) {
1313 			/* WRITE ERROR! */
1314 			fclose(output);
1315 			(void) unlink(temp_name);
1316 			return(-1);
1317 		}
1318 	}
1319 	fclose(output);
1320 
1321 	/* Rewind to before the terminating line */
1322 	body->seekg(here);
1323 	*outfile = new char[strlen(temp_name)+1];
1324 	strcpy(*outfile, temp_name);
1325 	return(0);
1326 }
No_Encode(FILE * input,IObottle * output)1327 int No_Encode(FILE *input, IObottle *output)
1328 {
1329 	char buffer[BUFSIZ];
1330 	unsigned int len;
1331 
1332 	while ( (len=fread(buffer, 1, sizeof(buffer), input)) > 0 ) {
1333 		if ( output->write(buffer, len) != len )
1334 			return(-1);
1335 	}
1336 	return(0);
1337 }
1338 
QP_Decode(IObottle * body,iconv_t iconv_ctx,char * endstrings[],int endlens[],char ** outfile)1339 int QP_Decode(IObottle *body, iconv_t iconv_ctx, char *endstrings[], int endlens[], char **outfile)
1340 {
1341 	long here = body->tellg();
1342 	char temp_name[PATH_MAX];
1343 	FILE *output;
1344 	char  line[BUFSIZ], newline[BUFSIZ], converted_line[BUFSIZ*2], *data;
1345 	int   i, j, n;
1346 	int   finished;
1347 
1348 	/* Grab a temporary file */
1349 	if ( (output=create_tempfile("w", temp_name)) == NULL )
1350 		return(-1);
1351 
1352 	if ( iconv_ctx != (iconv_t)-1 ) {
1353 		ConvertCharset(iconv_ctx, NULL, 0, NULL, 0);
1354 	}
1355 
1356 	/* Just DO it! :) */
1357 	for ( finished = 0; ! finished; ) {
1358 		here = body->tellg();
1359 
1360 		/* Read in the next line, checking for termination boundary */
1361 		body->readline(line, sizeof(line));
1362 		if ( body->eof() ) {
1363 			finished = 1;
1364 			continue;
1365 		}
1366 		for ( n=0; endstrings[n]; ++n ) {
1367 			if ( IsBoundary(line,endstrings[n],endlens[n]) )
1368 				break;
1369 		}
1370 		if ( endstrings[n] ) {
1371 			finished = 1;
1372 			continue;
1373 		}
1374 
1375 		/* Strip trailing whitespace */
1376 		for ( i = strlen(line); (i > 0) && isspace(line[i-1]); --i );
1377 		line[i] = '\0';
1378 
1379 		/* Convert lines from Quoted-Printable to raw data */
1380 		for ( i = 0, j = 0; line[i]; ++i, ++j ) {
1381 			if ( line[i] == '=' ) {
1382 				/* Check for soft return */
1383 				if ( line[i+1] == '\0' )
1384 					break;
1385 
1386 				/* Make sure no buffer overflow */
1387 				if ( ! line[i+1] || ! line[i+2] )
1388 					continue;
1389 
1390 				/* Convert from hexidecimal */
1391 				newline[j] = '\0';
1392 				if ( isdigit(line[++i]) )
1393 					newline[j] |= (line[i]-'0');
1394 				else
1395 					newline[j] |= (toupper(line[i])-'A'+10);
1396 				newline[j] <<= 4;
1397 				if ( isdigit(line[++i]) )
1398 					newline[j] |= (line[i]-'0');
1399 				else
1400 					newline[j] |= (toupper(line[i])-'A'+10);
1401 			} else
1402 				newline[j] = line[i];
1403 		}
1404 		if ( line[i] == '=' ) {
1405 			/* Soft Return, don't add a return to the stream */;
1406 		} else {
1407 			newline[j++] = '\n';
1408 		}
1409 		newline[j] = '\0';
1410 
1411 		/* Do any processing here */
1412 		if ( iconv_ctx != (iconv_t)-1 ) {
1413 			size_t len = ConvertCharset(iconv_ctx, newline, j, converted_line, sizeof(converted_line)-1);
1414 			converted_line[len] = '\0';
1415 			data = converted_line;
1416 		} else {
1417 			data = newline;
1418 		}
1419 
1420 		/* Write to the temporary file */
1421 		if ( fputs(data, output) == EOF ) {
1422 			/* WRITE ERROR! */
1423 			fclose(output);
1424 			(void) unlink(temp_name);
1425 			return(-1);
1426 		}
1427 	}
1428 	fclose(output);
1429 
1430 	/* Rewind to before the terminating line */
1431 	body->seekg(here);
1432 	*outfile = new char[strlen(temp_name)+1];
1433 	strcpy(*outfile, temp_name);
1434 	return(0);
1435 }
1436 /* A loose interpretation of the original... :-) */
1437 /* This implementation allows whitespace at the end of the line.
1438    I do this mainly because a bunch of whitespace representations at the
1439    ends of many lines looks really bad.  Personal taste. :-)
1440 */
QP_Encode(FILE * input,IObottle * output)1441 int QP_Encode(FILE *input, IObottle *output)
1442 {
1443 	char  buffer[BUFSIZ];
1444 	char  outline[80];
1445 	char *ptr;
1446 	unsigned int inlen, outlen;
1447 
1448 	outlen = 0;
1449 	while ( fgets(buffer, sizeof(buffer)-1, input) ) {
1450 		inlen = strlen(buffer);
1451 
1452 		/* The standard specifies no more than 76 chars per line */
1453 		for ( ptr=buffer; *ptr && (*ptr != '\n'); ++ptr ) {
1454 			/* Allowed non-encoded characters:
1455 				'\t', ' ', '!'-'<', '>'-'~' inc. A-Za-z
1456 			*/
1457 			if ( (*ptr == '\t') || (*ptr == ' ') ||
1458 					((*ptr >= 33) && (*ptr <= 60) ) ||
1459 					((*ptr >= 62) && (*ptr <= 126) ) ) {
1460 				outline[outlen++] = *ptr;
1461 
1462 				/* If we've hit soft limit, put soft return */
1463 				if ( outlen == 76 ) {
1464 					outline[outlen++] = '=';
1465 					outline[outlen++] = '\n';
1466 					if ( output->write(outline, outlen) !=
1467 								outlen ) {
1468 						return(-1);
1469 					}
1470 					outlen = 0;
1471 				}
1472 			} else {
1473 				/* If we've hit soft limit, put soft return */
1474 				if ( outlen >= (76-3) ) {
1475 					outline[outlen++] = '=';
1476 					outline[outlen++] = '\n';
1477 					if ( output->write(outline, outlen) !=
1478 								outlen ) {
1479 						return(-1);
1480 					}
1481 					outlen = 0;
1482 				}
1483 
1484 				/* Must encode */
1485 				sprintf(&outline[outlen], "=%2.2X", *ptr);
1486 				outlen += 3;
1487 			}
1488 		}
1489 
1490 		/* Take care of the (possible) newline */
1491 		if ( buffer[strlen(buffer)-1] == '\n' ) {
1492 			outline[outlen++] = '\n';
1493 			if ( output->write(outline, outlen) != outlen )
1494 				return(-1);
1495 			outlen = 0;
1496 		}
1497 	}
1498 	/* Flush any remaining output (file not terminated by newline) */
1499 	if ( outlen > 0 ) {
1500 		outline[outlen++] = '\n';
1501 		if ( output->write(outline, outlen) != outlen )
1502 			return(-1);
1503 		outlen = 0;
1504 	}
1505 	return(0);
1506 }
1507 
1508 /* Note, this hasn't been fully tested. */
Base64_Decode(IObottle * body,iconv_t iconv_ctx,char * endstrings[],int endlens[],char ** outfile)1509 int Base64_Decode(IObottle *body, iconv_t iconv_ctx, char *endstrings[], int endlens[], char **outfile)
1510 {
1511 	long here = body->tellg();
1512 	char temp_name[PATH_MAX];
1513 	FILE *output;
1514 	char  line[BUFSIZ], newline[BUFSIZ], converted_line[BUFSIZ*2], *data;
1515 	unsigned int n, newlen, finished;
1516 
1517 	/* Grab a temporary file */
1518 	if ( (output=create_tempfile("w", temp_name)) == NULL )
1519 		return(-1);
1520 
1521 	if ( iconv_ctx != (iconv_t)-1 ) {
1522 		ConvertCharset(iconv_ctx, NULL, 0, NULL, 0);
1523 	}
1524 
1525 	/* Just DO it! :) */
1526 	for ( finished = 0; ! finished; ) {
1527 		here = body->tellg();
1528 
1529 		/* Read in the next line, checking for termination boundary */
1530 		body->readline(line, sizeof(line));
1531 		if ( body->eof() ) {
1532 			finished = 1;
1533 			continue;
1534 		}
1535 		for ( n=0; endstrings[n]; ++n ) {
1536 			if ( IsBoundary(line,endstrings[n],endlens[n]) )
1537 				break;
1538 		}
1539 		if ( endstrings[n] ) {
1540 			finished = 1;
1541 			continue;
1542 		}
1543 
1544 		/* Do any processing here */
1545 		newlen = 0;
1546 		if ( (newlen=base64_decode(line, strlen(line), newline, sizeof(newline))) == 0 ) {
1547 			/* End of Base64 text -- eat up remaining text */
1548 			for ( ; ; ) {
1549 				here = body->tellg();
1550 
1551 				body->readline(line, sizeof(line));
1552 				if ( body->eof() )
1553 					break;
1554 				for ( n=0; endstrings[n]; ++n ) {
1555 					if ( IsBoundary(line, endstrings[n],
1556 								endlens[n]) )
1557 						break;
1558 				}
1559 				if ( endstrings[n] )
1560 					break;
1561 			}
1562 			break;
1563 		}
1564 		if ( iconv_ctx != (iconv_t)-1 ) {
1565 			newlen = ConvertCharset(iconv_ctx, newline, newlen, converted_line, sizeof(converted_line));
1566 			data = converted_line;
1567 		} else {
1568 			data = newline;
1569 		}
1570 
1571 
1572 		/* Write to the temporary file */
1573 		if ( fwrite(data, 1, newlen, output) != newlen ) {
1574 			/* WRITE ERROR! */
1575 			fclose(output);
1576 			(void) unlink(temp_name);
1577 			return(-1);
1578 		}
1579 	}
1580 	fclose(output);
1581 
1582 	/* Rewind to before the terminating line */
1583 	body->seekg(here);
1584 	*outfile = new char[strlen(temp_name)+1];
1585 	strcpy(*outfile, temp_name);
1586 	return(0);
1587 }
Base64_Encode(FILE * input,IObottle * output)1588 int Base64_Encode(FILE *input, IObottle *output)
1589 {
1590 	char buffer[19*3];
1591 	char outline[19*4+5];
1592 	unsigned int len;
1593 
1594 	/* Loop, reading in 19*3 bytes, transforming them into 19*4 bytes */
1595 	/*                  57                                 76         */
1596 	while ( (len=fread(buffer, 1, 19*3, input)) > 0 ) {
1597 		len = base64_encode(buffer, len, outline, sizeof(outline));
1598 		outline[len++] = '\n';
1599 		if ( output->write(outline, len) != len ) {
1600 			return(-1);
1601 		}
1602 	}
1603 	return(0);
1604 }
1605