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