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) const 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, 200 int32 index) const 201 { 202 BString string = HeaderField(key, index); 203 if (string == "") 204 return B_NAME_NOT_FOUND; 205 206 BString sub_cat; 207 BString end_piece; 208 int32 i = 0; 209 int32 end = 0; 210 211 // Break the header into parts, they're separated by semicolons, like this: 212 // Content-Type: multipart/mixed;boundary= "----=_NextPart_000_00AA_354DB459.5977A1CA" 213 // There's also white space and quotes to be removed, and even comments in 214 // parenthesis like this, which can appear anywhere white space is: (header comment) 215 216 while (end < string.Length()) { 217 end = string.FindFirst(';', i); 218 if (end < 0) 219 end = string.Length(); 220 221 string.CopyInto(sub_cat, i, end - i); 222 i = end + 1; 223 224 //-------Trim spaces off of beginning and end of text 225 for (int32 h = 0; h < sub_cat.Length(); h++) { 226 if (!isspace(sub_cat.ByteAt(h))) { 227 sub_cat.Remove(0, h); 228 break; 229 } 230 } 231 for (int32 h = sub_cat.Length() - 1; h >= 0; h--) { 232 if (!isspace(sub_cat.ByteAt(h))) { 233 sub_cat.Truncate(h + 1); 234 break; 235 } 236 } 237 //--------Split along '=' 238 int32 first_equal = sub_cat.FindFirst('='); 239 if (first_equal >= 0) { 240 sub_cat.CopyInto(end_piece, first_equal + 1, sub_cat.Length() - first_equal - 1); 241 sub_cat.Truncate(first_equal); 242 // Remove leading spaces from part after the equals sign. 243 while (isspace (end_piece.ByteAt(0))) 244 end_piece.Remove (0 /* index */, 1 /* number of chars */); 245 // Remove quote marks. 246 if (end_piece.ByteAt(0) == '\"') { 247 end_piece.Remove(0, 1); 248 end_piece.Truncate(end_piece.Length() - 1); 249 } 250 sub_cat.ToLower(); 251 structure->AddString(sub_cat.String(), end_piece.String()); 252 } else { 253 structure->AddString("unlabeled", sub_cat.String()); 254 } 255 } 256 257 return B_OK; 258 } 259 260 261 status_t 262 BMailComponent::RemoveHeader(const char* key) 263 { 264 return headers.RemoveName(key); 265 } 266 267 268 const char* 269 BMailComponent::HeaderAt(int32 index) const 270 { 271 #if defined(HAIKU_TARGET_PLATFORM_DANO) 272 const 273 #endif 274 char* name = NULL; 275 type_code type; 276 277 headers.GetInfo(B_STRING_TYPE, index, &name, &type); 278 return name; 279 } 280 281 282 status_t 283 BMailComponent::GetDecodedData(BPositionIO*) 284 { 285 return B_OK; 286 } 287 288 289 status_t 290 BMailComponent::SetDecodedData(BPositionIO*) 291 { 292 return B_OK; 293 } 294 295 296 status_t 297 BMailComponent::SetToRFC822(BPositionIO* data, size_t /*length*/, bool /*parse_now*/) 298 { 299 headers.MakeEmpty(); 300 301 // Only parse the header here 302 return parse_header(headers, *data); 303 } 304 305 306 status_t 307 BMailComponent::RenderToRFC822(BPositionIO* render_to) 308 { 309 int32 charset = B_ISO15_CONVERSION; 310 int8 encoding = quoted_printable; 311 const char* key; 312 const char* value; 313 char* allocd; 314 ssize_t amountWritten; 315 BString concat; 316 type_code stupidity_personified = B_STRING_TYPE; 317 int32 count = 0; 318 319 if (headers.HasInt32(kHeaderCharsetString)) 320 headers.FindInt32(kHeaderCharsetString, &charset); 321 if (headers.HasInt8(kHeaderEncodingString)) 322 headers.FindInt8(kHeaderEncodingString, &encoding); 323 324 for (int32 index = 0; headers.GetInfo(B_STRING_TYPE, index, 325 #if !defined(HAIKU_TARGET_PLATFORM_DANO) 326 (char**) 327 #endif 328 &key, &stupidity_personified, &count) == B_OK; index++) { 329 for (int32 g = 0; g < count; g++) { 330 headers.FindString(key, g, (const char**)&value); 331 allocd = (char*)malloc(strlen(value) + 1); 332 strcpy(allocd, value); 333 334 concat << key << ": "; 335 concat.CapitalizeEachWord(); 336 337 concat.Append(allocd, utf8_to_rfc2047(&allocd, strlen(value), 338 charset, encoding)); 339 free(allocd); 340 FoldLineAtWhiteSpaceAndAddCRLF(concat); 341 342 amountWritten = render_to->Write(concat.String(), concat.Length()); 343 if (amountWritten < 0) 344 return amountWritten; // IO error happened, usually disk full. 345 concat = ""; 346 } 347 } 348 349 render_to->Write("\r\n", 2); 350 351 return B_OK; 352 } 353 354 355 status_t 356 BMailComponent::MIMEType(BMimeType* mime) 357 { 358 bool foundBestHeader; 359 const char* boundaryString; 360 unsigned int i; 361 BMessage msg; 362 const char* typeAsString = NULL; 363 char typeAsLowerCaseString[B_MIME_TYPE_LENGTH]; 364 365 // Find the best Content-Type header to use. There should really be just 366 // one, but evil spammers sneakily insert one for multipart (with no 367 // boundary string), then one for text/plain. We'll scan through them and 368 // only use the multipart one if there are no others, and it has a 369 // boundary. 370 371 foundBestHeader = false; 372 for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) { 373 typeAsString = msg.FindString("unlabeled"); 374 if (typeAsString != NULL && strncasecmp(typeAsString, "multipart", 9) != 0) { 375 foundBestHeader = true; 376 break; 377 } 378 } 379 if (!foundBestHeader) { 380 for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) { 381 typeAsString = msg.FindString("unlabeled"); 382 if (typeAsString != NULL && strncasecmp(typeAsString, "multipart", 9) == 0) { 383 boundaryString = msg.FindString("boundary"); 384 if (boundaryString != NULL && strlen(boundaryString) > 0) { 385 foundBestHeader = true; 386 break; 387 } 388 } 389 } 390 } 391 // At this point we have the good MIME type in typeAsString, but only if 392 // foundBestHeader is true. 393 394 if (!foundBestHeader) { 395 strcpy(typeAsLowerCaseString, "text/plain"); // Hope this is an OK default. 396 } else { 397 // Some extra processing to convert mixed or upper case MIME types into 398 // lower case, since the BeOS R5 BMimeType is case sensitive (but Haiku 399 // isn't). Also truncate the string if it is too long. 400 for (i = 0; i < sizeof(typeAsLowerCaseString) - 1 401 && typeAsString[i] != 0; i++) 402 typeAsLowerCaseString[i] = tolower(typeAsString[i]); 403 typeAsLowerCaseString[i] = 0; 404 405 // Some old e-mail programs saved the type as just "TEXT", which we need to 406 // convert to "text/plain" since the rest of the code looks for that. 407 if (strcmp(typeAsLowerCaseString, "text") == 0) 408 strcpy(typeAsLowerCaseString, "text/plain"); 409 } 410 mime->SetTo(typeAsLowerCaseString); 411 return B_OK; 412 } 413 414 415 void BMailComponent::_ReservedComponent1() {} 416 void BMailComponent::_ReservedComponent2() {} 417 void BMailComponent::_ReservedComponent3() {} 418 void BMailComponent::_ReservedComponent4() {} 419 void BMailComponent::_ReservedComponent5() {} 420 421 422 //------------------------------------------------------------------------- 423 // #pragma mark - 424 425 426 BTextMailComponent::BTextMailComponent(const char* text, uint32 defaultCharSet) 427 : BMailComponent(defaultCharSet), 428 encoding(quoted_printable), 429 charset(B_ISO15_CONVERSION), 430 raw_data(NULL) 431 { 432 if (text != NULL) 433 SetText(text); 434 435 SetHeaderField("MIME-Version", "1.0"); 436 } 437 438 439 BTextMailComponent::~BTextMailComponent() 440 { 441 } 442 443 444 void 445 BTextMailComponent::SetEncoding(mail_encoding encoding, int32 charset) 446 { 447 this->encoding = encoding; 448 this->charset = charset; 449 } 450 451 452 void 453 BTextMailComponent::SetText(const char* text) 454 { 455 this->text.SetTo(text); 456 457 raw_data = NULL; 458 } 459 460 461 void 462 BTextMailComponent::AppendText(const char* text) 463 { 464 ParseRaw(); 465 466 this->text << text; 467 } 468 469 470 const char* 471 BTextMailComponent::Text() 472 { 473 ParseRaw(); 474 475 return text.String(); 476 } 477 478 479 BString* 480 BTextMailComponent::BStringText() 481 { 482 ParseRaw(); 483 484 return &text; 485 } 486 487 488 void 489 BTextMailComponent::Quote(const char* message, const char* quote_style) 490 { 491 ParseRaw(); 492 493 BString string; 494 string << '\n' << quote_style; 495 text.ReplaceAll("\n",string.String()); 496 497 string = message; 498 string << '\n'; 499 text.Prepend(string.String()); 500 } 501 502 503 status_t 504 BTextMailComponent::GetDecodedData(BPositionIO* data) 505 { 506 ParseRaw(); 507 508 if (data == NULL) 509 return B_IO_ERROR; 510 511 BMimeType type; 512 BMimeType textAny("text"); 513 ssize_t written; 514 if (MIMEType(&type) == B_OK && textAny.Contains(&type)) 515 // Write out the string which has been both decoded from quoted 516 // printable or base64 etc, and then converted to UTF-8 from whatever 517 // character set the message specified. Do it for text/html, 518 // text/plain and all other text datatypes. Of course, if the message 519 // is HTML and specifies a META tag for a character set, it will now be 520 // wrong. But then we don't display HTML in BeMail, yet. 521 written = data->Write(text.String(), text.Length()); 522 else 523 // Just write out whatever the binary contents are, only decoded from 524 // the quoted printable etc format. 525 written = data->Write(decoded.String(), decoded.Length()); 526 527 return written >= 0 ? B_OK : written; 528 } 529 530 531 status_t 532 BTextMailComponent::SetDecodedData(BPositionIO* data) 533 { 534 char buffer[255]; 535 size_t buf_len; 536 537 while ((buf_len = data->Read(buffer, 254)) > 0) { 538 buffer[buf_len] = 0; 539 this->text << buffer; 540 } 541 542 raw_data = NULL; 543 544 return B_OK; 545 } 546 547 548 status_t 549 BTextMailComponent::SetToRFC822(BPositionIO* data, size_t length, bool parseNow) 550 { 551 off_t position = data->Position(); 552 BMailComponent::SetToRFC822(data, length); 553 554 // Some malformed MIME headers can have the header running into the 555 // boundary of the next MIME chunk, resulting in a negative length. 556 length -= data->Position() - position; 557 if ((ssize_t) length < 0) 558 length = 0; 559 560 raw_data = data; 561 raw_length = length; 562 raw_offset = data->Position(); 563 564 if (parseNow) { 565 // copies the data stream and sets the raw_data variable to NULL 566 return ParseRaw(); 567 } 568 569 return B_OK; 570 } 571 572 573 status_t 574 BTextMailComponent::ParseRaw() 575 { 576 if (raw_data == NULL) 577 return B_OK; 578 579 raw_data->Seek(raw_offset, SEEK_SET); 580 581 BMessage content_type; 582 HeaderField("Content-Type", &content_type); 583 584 charset = _charSetForTextDecoding; 585 if (charset == B_MAIL_NULL_CONVERSION && content_type.HasString("charset")) { 586 const char* charset_string = content_type.FindString("charset"); 587 if (strcasecmp(charset_string, "us-ascii") == 0) { 588 charset = B_MAIL_US_ASCII_CONVERSION; 589 } else if (strcasecmp(charset_string, "utf-8") == 0) { 590 charset = B_MAIL_UTF8_CONVERSION; 591 } else { 592 const BCharacterSet* cs = BCharacterSetRoster::FindCharacterSetByName(charset_string); 593 if (cs != NULL) { 594 charset = cs->GetConversionID(); 595 } 596 } 597 } 598 599 encoding = encoding_for_cte(HeaderField("Content-Transfer-Encoding")); 600 601 char* buffer = (char*)malloc(raw_length + 1); 602 if (buffer == NULL) 603 return B_NO_MEMORY; 604 605 int32 bytes; 606 if ((bytes = raw_data->Read(buffer, raw_length)) < 0) 607 return B_IO_ERROR; 608 609 char* string = decoded.LockBuffer(bytes + 1); 610 bytes = decode(encoding, string, buffer, bytes, 0); 611 free(buffer); 612 buffer = NULL; 613 614 // Change line ends from \r\n to just \n. Though this won't work properly 615 // for UTF-16 because \r takes up two bytes rather than one. 616 char* dest; 617 char* src; 618 char* end = string + bytes; 619 for (dest = src = string; src < end; src++) { 620 if (*src != '\r') 621 *dest++ = *src; 622 } 623 decoded.UnlockBuffer(dest - string); 624 bytes = decoded.Length(); // Might have shrunk a bit. 625 626 // If the character set wasn't specified, try to guess. ISO-2022-JP 627 // contains the escape sequences ESC $ B or ESC $ @ to turn on 2 byte 628 // Japanese, and ESC ( J to switch to Roman, or sometimes ESC ( B for 629 // ASCII. We'll just try looking for the two switch to Japanese sequences. 630 631 if (charset == B_MAIL_NULL_CONVERSION) { 632 if (decoded.FindFirst ("\e$B") >= 0 || decoded.FindFirst ("\e$@") >= 0) 633 charset = B_JIS_CONVERSION; 634 else // Just assume the usual Latin-9 character set. 635 charset = B_ISO15_CONVERSION; 636 } 637 638 int32 state = 0; 639 int32 destLength = bytes * 3 /* in case it grows */ + 1 /* +1 so it isn't zero which crashes */; 640 string = text.LockBuffer(destLength); 641 mail_convert_to_utf8(charset, decoded.String(), &bytes, string, 642 &destLength, &state); 643 if (destLength > 0) 644 text.UnlockBuffer(destLength); 645 else { 646 text.UnlockBuffer(0); 647 text.SetTo(decoded); 648 } 649 650 raw_data = NULL; 651 return B_OK; 652 } 653 654 655 status_t 656 BTextMailComponent::RenderToRFC822(BPositionIO* render_to) 657 { 658 status_t status = ParseRaw(); 659 if (status < B_OK) 660 return status; 661 662 BMimeType type; 663 MIMEType(&type); 664 BString content_type; 665 content_type << type.Type(); // Preserve MIME type (e.g. text/html 666 667 for (uint32 i = 0; mail_charsets[i].charset != NULL; i++) { 668 if (mail_charsets[i].flavor == charset) { 669 content_type << "; charset=\"" << mail_charsets[i].charset << "\""; 670 break; 671 } 672 } 673 674 SetHeaderField("Content-Type", content_type.String()); 675 676 const char* transfer_encoding = NULL; 677 switch (encoding) { 678 case base64: 679 transfer_encoding = "base64"; 680 break; 681 case quoted_printable: 682 transfer_encoding = "quoted-printable"; 683 break; 684 case eight_bit: 685 transfer_encoding = "8bit"; 686 break; 687 case seven_bit: 688 default: 689 transfer_encoding = "7bit"; 690 break; 691 } 692 693 SetHeaderField("Content-Transfer-Encoding", transfer_encoding); 694 695 BMailComponent::RenderToRFC822(render_to); 696 697 BString modified = this->text; 698 BString alt; 699 700 int32 len = this->text.Length(); 701 if (len > 0) { 702 int32 dest_len = len * 5; 703 // Shift-JIS can have a 3 byte escape sequence and a 2 byte code for 704 // each character (which could just be 2 bytes in UTF-8, or even 1 byte 705 // if it's regular ASCII), so it can get quite a bit larger than the 706 // original text. Multiplying by 5 should make more than enough space. 707 char* raw = alt.LockBuffer(dest_len); 708 int32 state = 0; 709 mail_convert_from_utf8(charset, this->text.String(), &len, raw, 710 &dest_len, &state); 711 alt.UnlockBuffer(dest_len); 712 713 raw = modified.LockBuffer((alt.Length() * 3) + 1); 714 switch (encoding) { 715 case base64: 716 len = encode_base64(raw, alt.String(), alt.Length(), false); 717 raw[len] = 0; 718 break; 719 case quoted_printable: 720 len = encode_qp(raw, alt.String(), alt.Length(), false); 721 raw[len] = 0; 722 break; 723 case eight_bit: 724 case seven_bit: 725 default: 726 len = alt.Length(); 727 strcpy(raw, alt.String()); 728 } 729 modified.UnlockBuffer(len); 730 731 if (encoding != base64) // encode_base64 already does CRLF line endings. 732 modified.ReplaceAll("\n","\r\n"); 733 734 // There seem to be a possibility of NULL bytes in the text, so lets 735 // filter them out, shouldn't be any after the encoding stage. 736 737 char* string = modified.LockBuffer(modified.Length()); 738 for (int32 i = modified.Length(); i-- > 0;) { 739 if (string[i] != '\0') 740 continue; 741 742 puts("BTextMailComponent::RenderToRFC822: NULL byte in text!!"); 743 string[i] = ' '; 744 } 745 modified.UnlockBuffer(); 746 747 // word wrapping is already done by BeMail (user-configurable) 748 // and it does it *MUCH* nicer. 749 750 // //------Desperate bid to wrap lines 751 // int32 curr_line_length = 0; 752 // int32 last_space = 0; 753 // 754 // for (int32 i = 0; i < modified.Length(); i++) { 755 // if (isspace(modified.ByteAt(i))) 756 // last_space = i; 757 // 758 // if ((modified.ByteAt(i) == '\r') && (modified.ByteAt(i+1) == '\n')) 759 // curr_line_length = 0; 760 // else 761 // curr_line_length++; 762 // 763 // if (curr_line_length > 80) { 764 // if (last_space >= 0) { 765 // modified.Insert("\r\n",last_space); 766 // last_space = -1; 767 // curr_line_length = 0; 768 // } 769 // } 770 // } 771 } 772 modified << "\r\n"; 773 774 render_to->Write(modified.String(), modified.Length()); 775 776 return B_OK; 777 } 778 779 780 void BTextMailComponent::_ReservedText1() {} 781 void BTextMailComponent::_ReservedText2() {} 782