xref: /haiku/src/kits/mail/MailComponent.cpp (revision bf57c148f7787f0df15980976997c6dfb70ee067)
1 /* (Text)Component - message component base class and plain text
2 **
3 ** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
4 */
5 
6 
7 #include <String.h>
8 #include <Mime.h>
9 
10 #include <ctype.h>
11 #include <stdlib.h>
12 #include <strings.h>
13 
14 class _EXPORT BMailComponent;
15 class _EXPORT BTextMailComponent;
16 
17 #include <MailComponent.h>
18 #include <MailAttachment.h>
19 #include <MailContainer.h>
20 #include <mail_util.h>
21 
22 #include <CharacterSet.h>
23 #include <CharacterSetRoster.h>
24 
25 using namespace BPrivate ;
26 
27 struct CharsetConversionEntry
28 {
29 	const char* charset;
30 	uint32 flavor;
31 };
32 
33 extern const CharsetConversionEntry mail_charsets[];
34 
35 
36 const char* kHeaderCharsetString = "header-charset";
37 const char* kHeaderEncodingString = "header-encoding";
38 // Special field names in the headers which specify the character set (int32)
39 // and encoding (int8) to use when converting the headers from UTF-8 to the
40 // output e-mail format (rfc2047).  Since they are numbers, not strings, the
41 // extra fields won't be output.
42 
43 
44 BMailComponent::BMailComponent(uint32 defaultCharSet)
45 	: _charSetForTextDecoding (defaultCharSet)
46 {
47 }
48 
49 
50 BMailComponent::~BMailComponent()
51 {
52 }
53 
54 
55 uint32
56 BMailComponent::ComponentType()
57 {
58 	if (NULL != dynamic_cast<BAttributedMailAttachment*> (this))
59 		return B_MAIL_ATTRIBUTED_ATTACHMENT;
60 
61 	BMimeType type, super;
62 	MIMEType(&type);
63 	type.GetSupertype(&super);
64 
65 	//---------ATT-This code *desperately* needs to be improved
66 	if (super == "multipart") {
67 		if (type == "multipart/x-bfile") // Not likely, they have the MIME
68 			return B_MAIL_ATTRIBUTED_ATTACHMENT; // of their data contents.
69 		else
70 			return B_MAIL_MULTIPART_CONTAINER;
71 	} else if (!IsAttachment() && (super == "text" || type.Type() == NULL))
72 		return B_MAIL_PLAIN_TEXT_BODY;
73 	else
74 		return B_MAIL_SIMPLE_ATTACHMENT;
75 }
76 
77 
78 BMailComponent*
79 BMailComponent::WhatIsThis()
80 {
81 	switch (ComponentType()) {
82 		case B_MAIL_SIMPLE_ATTACHMENT:
83 			return new BSimpleMailAttachment;
84 		case B_MAIL_ATTRIBUTED_ATTACHMENT:
85 			return new BAttributedMailAttachment;
86 		case B_MAIL_MULTIPART_CONTAINER:
87 			return new BMIMEMultipartMailContainer (NULL, NULL, _charSetForTextDecoding);
88 		case B_MAIL_PLAIN_TEXT_BODY:
89 		default:
90 			return new BTextMailComponent (NULL, _charSetForTextDecoding);
91 	}
92 }
93 
94 
95 bool
96 BMailComponent::IsAttachment()
97 {
98 	const char* disposition = HeaderField("Content-Disposition");
99 	if ((disposition != NULL)
100 		&& (strncasecmp(disposition, "Attachment", strlen("Attachment")) == 0))
101 		return true;
102 
103 	BMessage header;
104 	HeaderField("Content-Type", &header);
105 	if (header.HasString("name"))
106 		return true;
107 
108 	if (HeaderField("Content-Location", &header) == B_OK)
109 		return true;
110 
111 	BMimeType type;
112 	MIMEType(&type);
113 	if (type == "multipart/x-bfile")
114 		return true;
115 
116 	return false;
117 }
118 
119 
120 void
121 BMailComponent::SetHeaderField(const char* key, const char* value,
122 	uint32 charset, mail_encoding encoding, bool replace_existing)
123 {
124 	if (replace_existing)
125 		headers.RemoveName(key);
126 	if (value != NULL && value[0] != 0) // Empty or NULL strings mean delete header.
127 		headers.AddString(key, value);
128 
129 	// Latest setting of the character set and encoding to use when outputting
130 	// the headers is the one which affects all the headers.  There used to be
131 	// separate settings for each item in the headers, but it never actually
132 	// worked (can't store multiple items of different types in a BMessage).
133 	if (charset != B_MAIL_NULL_CONVERSION
134 		&& headers.ReplaceInt32 (kHeaderCharsetString, charset) != B_OK)
135 		headers.AddInt32(kHeaderCharsetString, charset);
136 	if (encoding != null_encoding
137 		&& headers.ReplaceInt8 (kHeaderEncodingString, encoding) != B_OK)
138 		headers.AddInt8(kHeaderEncodingString, encoding);
139 }
140 
141 
142 void
143 BMailComponent::SetHeaderField(const char* key, BMessage* structure,
144 	bool replace_existing)
145 {
146 	int32 charset = B_MAIL_NULL_CONVERSION;
147 	int8 encoding = null_encoding;
148 	const char* unlabeled = "unlabeled";
149 
150 	if (replace_existing)
151 		headers.RemoveName(key);
152 
153 	BString value;
154 	if (structure->HasString(unlabeled))
155 		value << structure->FindString(unlabeled) << "; ";
156 
157 	const char* name;
158 	const char* sub_val;
159 	type_code type;
160 	for (int32 i = 0; structure->GetInfo(B_STRING_TYPE, i,
161 #if !defined(HAIKU_TARGET_PLATFORM_DANO)
162 		(char**)
163 #endif
164 		&name, &type) == B_OK; i++) {
165 
166 		if (strcasecmp(name, unlabeled) == 0)
167 			continue;
168 
169 		structure->FindString(name, &sub_val);
170 		value << name << '=';
171 		if (BString(sub_val).FindFirst(' ') > 0)
172 			value << '\"' << sub_val << "\"; ";
173 		else
174 			value << sub_val << "; ";
175 	}
176 
177 	value.Truncate(value.Length() - 2); //-----Remove the last "; "
178 
179 	if (structure->HasInt32(kHeaderCharsetString))
180 		structure->FindInt32(kHeaderCharsetString, &charset);
181 	if (structure->HasInt8(kHeaderEncodingString))
182 		structure->FindInt8(kHeaderEncodingString, &encoding);
183 
184 	SetHeaderField(key, value.String(), (uint32) charset, (mail_encoding) encoding);
185 }
186 
187 
188 const char*
189 BMailComponent::HeaderField(const char* key, int32 index)
190 {
191 	const char* string = NULL;
192 
193 	headers.FindString(key, index, &string);
194 	return string;
195 }
196 
197 
198 status_t
199 BMailComponent::HeaderField(const char* key, BMessage* structure, int32 index)
200 {
201 	BString string = HeaderField(key, index);
202 	if (string == "")
203 		return B_NAME_NOT_FOUND;
204 
205 	BString sub_cat;
206 	BString end_piece;
207 	int32 i = 0;
208 	int32 end = 0;
209 
210 	// Break the header into parts, they're separated by semicolons, like this:
211 	// Content-Type: multipart/mixed;boundary= "----=_NextPart_000_00AA_354DB459.5977A1CA"
212 	// There's also white space and quotes to be removed, and even comments in
213 	// parenthesis like this, which can appear anywhere white space is: (header comment)
214 
215 	while (end < string.Length()) {
216 		end = string.FindFirst(';', i);
217 		if (end < 0)
218 			end = string.Length();
219 
220 		string.CopyInto(sub_cat, i, end - i);
221 		i = end + 1;
222 
223 		//-------Trim spaces off of beginning and end of text
224 		for (int32 h = 0; h < sub_cat.Length(); h++) {
225 			if (!isspace(sub_cat.ByteAt(h))) {
226 				sub_cat.Remove(0, h);
227 				break;
228 			}
229 		}
230 		for (int32 h = sub_cat.Length() - 1; h >= 0; h--) {
231 			if (!isspace(sub_cat.ByteAt(h))) {
232 				sub_cat.Truncate(h + 1);
233 				break;
234 			}
235 		}
236 		//--------Split along '='
237 		int32 first_equal = sub_cat.FindFirst('=');
238 		if (first_equal >= 0) {
239 			sub_cat.CopyInto(end_piece, first_equal + 1, sub_cat.Length() - first_equal - 1);
240 			sub_cat.Truncate(first_equal);
241 			// Remove leading spaces from part after the equals sign.
242 			while (isspace (end_piece.ByteAt(0)))
243 				end_piece.Remove (0 /* index */, 1 /* number of chars */);
244 			// Remove quote marks.
245 			if (end_piece.ByteAt(0) == '\"') {
246 				end_piece.Remove(0, 1);
247 				end_piece.Truncate(end_piece.Length() - 1);
248 			}
249 			sub_cat.ToLower();
250 			structure->AddString(sub_cat.String(), end_piece.String());
251 		} else {
252 			structure->AddString("unlabeled", sub_cat.String());
253 		}
254 	}
255 
256 	return B_OK;
257 }
258 
259 
260 status_t
261 BMailComponent::RemoveHeader(const char* key)
262 {
263 	return headers.RemoveName(key);
264 }
265 
266 
267 const char*
268 BMailComponent::HeaderAt(int32 index)
269 {
270 #if defined(HAIKU_TARGET_PLATFORM_DANO)
271 	const
272 #endif
273 	char* name = NULL;
274 	type_code type;
275 
276 	headers.GetInfo(B_STRING_TYPE, index, &name, &type);
277 	return name;
278 }
279 
280 
281 status_t
282 BMailComponent::GetDecodedData(BPositionIO*)
283 {
284 	return B_OK;
285 }
286 
287 
288 status_t
289 BMailComponent::SetDecodedData(BPositionIO*)
290 {
291 	return B_OK;
292 }
293 
294 
295 status_t
296 BMailComponent::SetToRFC822(BPositionIO* data, size_t /*length*/, bool /*parse_now*/)
297 {
298 	headers.MakeEmpty();
299 
300 	// Only parse the header here
301 	return parse_header(headers, *data);
302 }
303 
304 
305 status_t
306 BMailComponent::RenderToRFC822(BPositionIO* render_to)
307 {
308 	int32 charset = B_ISO15_CONVERSION;
309 	int8 encoding = quoted_printable;
310 	const char* key;
311 	const char* value;
312 	char* allocd;
313 	ssize_t amountWritten;
314 	BString concat;
315 	type_code stupidity_personified = B_STRING_TYPE;
316 	int32 count = 0;
317 
318 	if (headers.HasInt32(kHeaderCharsetString))
319 		headers.FindInt32(kHeaderCharsetString, &charset);
320 	if (headers.HasInt8(kHeaderEncodingString))
321 		headers.FindInt8(kHeaderEncodingString, &encoding);
322 
323 	for (int32 index = 0; headers.GetInfo(B_STRING_TYPE, index,
324 #if !defined(HAIKU_TARGET_PLATFORM_DANO)
325 			(char**)
326 #endif
327 			&key, &stupidity_personified, &count) == B_OK; index++) {
328 		for (int32 g = 0; g < count; g++) {
329 			headers.FindString(key, g, (const char**)&value);
330 			allocd = (char*)malloc(strlen(value) + 1);
331 			strcpy(allocd, value);
332 
333 			concat << key << ": ";
334 			concat.CapitalizeEachWord();
335 
336 			concat.Append(allocd, utf8_to_rfc2047(&allocd, strlen(value),
337 				charset, encoding));
338 			free(allocd);
339 			FoldLineAtWhiteSpaceAndAddCRLF(concat);
340 
341 			amountWritten = render_to->Write(concat.String(), concat.Length());
342 			if (amountWritten < 0)
343 				return amountWritten; // IO error happened, usually disk full.
344 			concat = "";
345 		}
346 	}
347 
348 	render_to->Write("\r\n", 2);
349 
350 	return B_OK;
351 }
352 
353 
354 status_t
355 BMailComponent::MIMEType(BMimeType* mime)
356 {
357 	bool foundBestHeader;
358 	const char* boundaryString;
359 	unsigned int i;
360 	BMessage msg;
361 	const char* typeAsString = NULL;
362 	char typeAsLowerCaseString[B_MIME_TYPE_LENGTH];
363 
364 	// Find the best Content-Type header to use.  There should really be just
365 	// one, but evil spammers sneakily insert one for multipart (with no
366 	// boundary string), then one for text/plain.  We'll scan through them and
367 	// only use the multipart one if there are no others, and it has a
368 	// boundary.
369 
370 	foundBestHeader = false;
371 	for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) {
372 		typeAsString = msg.FindString("unlabeled");
373 		if (typeAsString != NULL && strncasecmp(typeAsString, "multipart", 9) != 0) {
374 			foundBestHeader = true;
375 			break;
376 		}
377 	}
378 	if (!foundBestHeader) {
379 		for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) {
380 			typeAsString = msg.FindString("unlabeled");
381 			if (typeAsString != NULL && strncasecmp(typeAsString, "multipart", 9) == 0) {
382 				boundaryString = msg.FindString("boundary");
383 				if (boundaryString != NULL && strlen(boundaryString) > 0) {
384 					foundBestHeader = true;
385 					break;
386 				}
387 			}
388 		}
389 	}
390 	// At this point we have the good MIME type in typeAsString, but only if
391 	// foundBestHeader is true.
392 
393 	if (!foundBestHeader) {
394 		strcpy(typeAsLowerCaseString, "text/plain"); // Hope this is an OK default.
395 	} else {
396 		// Some extra processing to convert mixed or upper case MIME types into
397 		// lower case, since the BeOS R5 BMimeType is case sensitive (but OpenBeOS
398 		// isn't).  Also truncate the string if it is too long.
399 		for (i = 0; i < sizeof(typeAsLowerCaseString) - 1
400 			&& typeAsString[i] != 0; i++)
401 			typeAsLowerCaseString[i] = tolower(typeAsString[i]);
402 		typeAsLowerCaseString[i] = 0;
403 
404 		// Some old e-mail programs saved the type as just "TEXT", which we need to
405 		// convert to "text/plain" since the rest of the code looks for that.
406 		if (strcmp(typeAsLowerCaseString, "text") == 0)
407 			strcpy(typeAsLowerCaseString, "text/plain");
408 	}
409 	mime->SetTo(typeAsLowerCaseString);
410 	return B_OK;
411 }
412 
413 
414 void BMailComponent::_ReservedComponent1() {}
415 void BMailComponent::_ReservedComponent2() {}
416 void BMailComponent::_ReservedComponent3() {}
417 void BMailComponent::_ReservedComponent4() {}
418 void BMailComponent::_ReservedComponent5() {}
419 
420 
421 //-------------------------------------------------------------------------
422 //	#pragma mark -
423 
424 
425 BTextMailComponent::BTextMailComponent(const char* text, uint32 defaultCharSet)
426 	: BMailComponent(defaultCharSet),
427 	encoding(quoted_printable),
428 	charset(B_ISO15_CONVERSION),
429 	raw_data(NULL)
430 {
431 	if (text != NULL)
432 		SetText(text);
433 
434 	SetHeaderField("MIME-Version", "1.0");
435 }
436 
437 
438 BTextMailComponent::~BTextMailComponent()
439 {
440 }
441 
442 
443 void
444 BTextMailComponent::SetEncoding(mail_encoding encoding, int32 charset)
445 {
446 	this->encoding = encoding;
447 	this->charset = charset;
448 }
449 
450 
451 void
452 BTextMailComponent::SetText(const char* text)
453 {
454 	this->text.SetTo(text);
455 
456 	raw_data = NULL;
457 }
458 
459 
460 void
461 BTextMailComponent::AppendText(const char* text)
462 {
463 	ParseRaw();
464 
465 	this->text << text;
466 }
467 
468 
469 const char*
470 BTextMailComponent::Text()
471 {
472 	ParseRaw();
473 
474 	return text.String();
475 }
476 
477 
478 BString*
479 BTextMailComponent::BStringText()
480 {
481 	ParseRaw();
482 
483 	return &text;
484 }
485 
486 
487 void
488 BTextMailComponent::Quote(const char* message, const char* quote_style)
489 {
490 	ParseRaw();
491 
492 	BString string;
493 	string << '\n' << quote_style;
494 	text.ReplaceAll("\n",string.String());
495 
496 	string = message;
497 	string << '\n';
498 	text.Prepend(string.String());
499 }
500 
501 
502 status_t
503 BTextMailComponent::GetDecodedData(BPositionIO* data)
504 {
505 	ParseRaw();
506 
507 	if (data == NULL)
508 		return B_IO_ERROR;
509 
510 	BMimeType type;
511 	BMimeType textAny("text");
512 	ssize_t written;
513 	if (MIMEType(&type) == B_OK && textAny.Contains(&type))
514 		// Write out the string which has been both decoded from quoted
515 		// printable or base64 etc, and then converted to UTF-8 from whatever
516 		// character set the message specified.  Do it for text/html,
517 		// text/plain and all other text datatypes.  Of course, if the message
518 		// is HTML and specifies a META tag for a character set, it will now be
519 		// wrong.  But then we don't display HTML in BeMail, yet.
520 		written = data->Write(text.String(), text.Length());
521 	else
522 		// Just write out whatever the binary contents are, only decoded from
523 		// the quoted printable etc format.
524 		written = data->Write(decoded.String(), decoded.Length());
525 
526 	return written >= 0 ? B_OK : written;
527 }
528 
529 
530 status_t
531 BTextMailComponent::SetDecodedData(BPositionIO* data)
532 {
533 	char buffer[255];
534 	size_t buf_len;
535 
536 	while ((buf_len = data->Read(buffer, 254)) > 0) {
537 		buffer[buf_len] = 0;
538 		this->text << buffer;
539 	}
540 
541 	raw_data = NULL;
542 
543 	return B_OK;
544 }
545 
546 
547 status_t
548 BTextMailComponent::SetToRFC822(BPositionIO* data, size_t length, bool parseNow)
549 {
550 	off_t position = data->Position();
551 	BMailComponent::SetToRFC822(data, length);
552 
553 	// Some malformed MIME headers can have the header running into the
554 	// boundary of the next MIME chunk, resulting in a negative length.
555 	length -= data->Position() - position;
556 	if ((ssize_t) length < 0)
557 	  length = 0;
558 
559 	raw_data = data;
560 	raw_length = length;
561 	raw_offset = data->Position();
562 
563 	if (parseNow) {
564 		// copies the data stream and sets the raw_data variable to NULL
565 		return ParseRaw();
566 	}
567 
568 	return B_OK;
569 }
570 
571 
572 status_t
573 BTextMailComponent::ParseRaw()
574 {
575 	if (raw_data == NULL)
576 		return B_OK;
577 
578 	raw_data->Seek(raw_offset, SEEK_SET);
579 
580 	BMessage content_type;
581 	HeaderField("Content-Type", &content_type);
582 
583 	charset = _charSetForTextDecoding;
584 	if (charset == B_MAIL_NULL_CONVERSION && content_type.HasString("charset")) {
585 		const char* charset_string = content_type.FindString("charset");
586 		if (strcasecmp(charset_string, "us-ascii") == 0) {
587 			charset = B_MAIL_US_ASCII_CONVERSION;
588 		} else if (strcasecmp(charset_string, "utf-8") == 0) {
589 			charset = B_MAIL_UTF8_CONVERSION;
590 		} else {
591 			const BCharacterSet* cs = BCharacterSetRoster::FindCharacterSetByName(charset_string);
592 			if (cs != NULL) {
593 				charset = cs->GetConversionID();
594 			}
595 		}
596 	}
597 
598 	encoding = encoding_for_cte(HeaderField("Content-Transfer-Encoding"));
599 
600 	char* buffer = (char*)malloc(raw_length + 1);
601 	if (buffer == NULL)
602 		return B_NO_MEMORY;
603 
604 	int32 bytes;
605 	if ((bytes = raw_data->Read(buffer, raw_length)) < 0)
606 		return B_IO_ERROR;
607 
608 	char* string = decoded.LockBuffer(bytes + 1);
609 	bytes = decode(encoding, string, buffer, bytes, 0);
610 	free(buffer);
611 	buffer = NULL;
612 
613 	// Change line ends from \r\n to just \n.  Though this won't work properly
614 	// for UTF-16 because \r takes up two bytes rather than one.
615 	char* dest;
616 	char* src;
617 	char* end = string + bytes;
618 	for (dest = src = string; src < end; src++) {
619 	 	if (*src != '\r')
620 	 		*dest++ = *src;
621 	}
622 	decoded.UnlockBuffer(dest - string);
623 	bytes = decoded.Length(); // Might have shrunk a bit.
624 
625 	// If the character set wasn't specified, try to guess.  ISO-2022-JP
626 	// contains the escape sequences ESC $ B or ESC $ @ to turn on 2 byte
627 	// Japanese, and ESC ( J to switch to Roman, or sometimes ESC ( B for
628 	// ASCII.  We'll just try looking for the two switch to Japanese sequences.
629 
630 	if (charset == B_MAIL_NULL_CONVERSION) {
631 		if (decoded.FindFirst ("\e$B") >= 0 || decoded.FindFirst ("\e$@") >= 0)
632 			charset = B_JIS_CONVERSION;
633 		else // Just assume the usual Latin-9 character set.
634 			charset = B_ISO15_CONVERSION;
635 	}
636 
637 	int32 state = 0;
638 	int32 destLength = bytes * 3 /* in case it grows */ + 1 /* +1 so it isn't zero which crashes */;
639 	string = text.LockBuffer(destLength);
640 	mail_convert_to_utf8(charset, decoded.String(), &bytes, string,
641 		&destLength, &state);
642 	if (destLength > 0)
643 		text.UnlockBuffer(destLength);
644 	else {
645 		text.UnlockBuffer(0);
646 		text.SetTo(decoded);
647 	}
648 
649 	raw_data = NULL;
650 	return B_OK;
651 }
652 
653 
654 status_t
655 BTextMailComponent::RenderToRFC822(BPositionIO* render_to)
656 {
657 	status_t status = ParseRaw();
658 	if (status < B_OK)
659 		return status;
660 
661 	BMimeType type;
662 	MIMEType(&type);
663 	BString content_type;
664 	content_type << type.Type(); // Preserve MIME type (e.g. text/html
665 
666 	for (uint32 i = 0; mail_charsets[i].charset != NULL; i++) {
667 		if (mail_charsets[i].flavor == charset) {
668 			content_type << "; charset=\"" << mail_charsets[i].charset << "\"";
669 			break;
670 		}
671 	}
672 
673 	SetHeaderField("Content-Type", content_type.String());
674 
675 	const char* transfer_encoding = NULL;
676 	switch (encoding) {
677 		case base64:
678 			transfer_encoding = "base64";
679 			break;
680 		case quoted_printable:
681 			transfer_encoding = "quoted-printable";
682 			break;
683 		case eight_bit:
684 			transfer_encoding = "8bit";
685 			break;
686 		case seven_bit:
687 		default:
688 			transfer_encoding = "7bit";
689 			break;
690 	}
691 
692 	SetHeaderField("Content-Transfer-Encoding", transfer_encoding);
693 
694 	BMailComponent::RenderToRFC822(render_to);
695 
696 	BString modified = this->text;
697 	BString alt;
698 
699 	int32 len = this->text.Length();
700 	if (len > 0) {
701 		int32 dest_len = len * 5;
702 		// Shift-JIS can have a 3 byte escape sequence and a 2 byte code for
703 		// each character (which could just be 2 bytes in UTF-8, or even 1 byte
704 		// if it's regular ASCII), so it can get quite a bit larger than the
705 		// original text.  Multiplying by 5 should make more than enough space.
706 		char* raw = alt.LockBuffer(dest_len);
707 		int32 state = 0;
708 		mail_convert_from_utf8(charset, this->text.String(), &len, raw,
709 			&dest_len, &state);
710 		alt.UnlockBuffer(dest_len);
711 
712 		raw = modified.LockBuffer((alt.Length() * 3) + 1);
713 		switch (encoding) {
714 			case base64:
715 				len = encode_base64(raw, alt.String(), alt.Length(), false);
716 				raw[len] = 0;
717 				break;
718 			case quoted_printable:
719 				len = encode_qp(raw, alt.String(), alt.Length(), false);
720 				raw[len] = 0;
721 				break;
722 			case eight_bit:
723 			case seven_bit:
724 			default:
725 				len = alt.Length();
726 				strcpy(raw, alt.String());
727 		}
728 		modified.UnlockBuffer(len);
729 
730 		if (encoding != base64) // encode_base64 already does CRLF line endings.
731 			modified.ReplaceAll("\n","\r\n");
732 
733 		// There seem to be a possibility of NULL bytes in the text, so lets
734 		// filter them out, shouldn't be any after the encoding stage.
735 
736 		char* string = modified.LockBuffer(modified.Length());
737 		for (int32 i = modified.Length(); i-- > 0;) {
738 			if (string[i] != '\0')
739 				continue;
740 
741 			puts("BTextMailComponent::RenderToRFC822: NULL byte in text!!");
742 			string[i] = ' ';
743 		}
744 		modified.UnlockBuffer();
745 
746 		// word wrapping is already done by BeMail (user-configurable)
747 		// and it does it *MUCH* nicer.
748 
749 //		//------Desperate bid to wrap lines
750 //		int32 curr_line_length = 0;
751 //		int32 last_space = 0;
752 //
753 //		for (int32 i = 0; i < modified.Length(); i++) {
754 //			if (isspace(modified.ByteAt(i)))
755 //				last_space = i;
756 //
757 //			if ((modified.ByteAt(i) == '\r') && (modified.ByteAt(i+1) == '\n'))
758 //				curr_line_length = 0;
759 //			else
760 //				curr_line_length++;
761 //
762 //			if (curr_line_length > 80) {
763 //				if (last_space >= 0) {
764 //					modified.Insert("\r\n",last_space);
765 //					last_space = -1;
766 //					curr_line_length = 0;
767 //				}
768 //			}
769 //		}
770 	}
771 	modified << "\r\n";
772 
773 	render_to->Write(modified.String(), modified.Length());
774 
775 	return B_OK;
776 }
777 
778 
779 void BTextMailComponent::_ReservedText1() {}
780 void BTextMailComponent::_ReservedText2() {}
781