/* * Copyright 2007-2015, Haiku Inc. All Rights Reserved. * Copyright 2001-2004 Dr. Zoidberg Enterprises. All rights reserved. * * Distributed under the terms of the MIT License. */ //! The main general purpose mail message class #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace BPrivate; //-------Change the following!---------------------- #define mime_boundary "----------Zoidberg-BeMail-temp--------" #define mime_warning "This is a multipart message in MIME format." BEmailMessage::BEmailMessage(BPositionIO* file, bool own, uint32 defaultCharSet) : BMailContainer(defaultCharSet), fData(NULL), fStatus(B_NO_ERROR), fBCC(NULL), fComponentCount(0), fBody(NULL), fTextBody(NULL) { BMailSettings settings; fAccountID = settings.DefaultOutboundAccount(); if (own) fData = file; if (file != NULL) SetToRFC822(file, ~0L); } BEmailMessage::BEmailMessage(const entry_ref* ref, uint32 defaultCharSet) : BMailContainer(defaultCharSet), fBCC(NULL), fComponentCount(0), fBody(NULL), fTextBody(NULL) { BMailSettings settings; fAccountID = settings.DefaultOutboundAccount(); fData = new BFile(); fStatus = static_cast(fData)->SetTo(ref, B_READ_ONLY); if (fStatus == B_OK) SetToRFC822(fData, ~0L); } BEmailMessage::~BEmailMessage() { free(fBCC); delete fBody; delete fData; } status_t BEmailMessage::InitCheck() const { return fStatus; } BEmailMessage* BEmailMessage::ReplyMessage(mail_reply_to_mode replyTo, bool accountFromMail, const char* quoteStyle) { BEmailMessage* reply = new BEmailMessage; // Set ReplyTo: if (replyTo == B_MAIL_REPLY_TO_ALL) { reply->SetTo(From()); BList list; get_address_list(list, CC(), extract_address); get_address_list(list, To(), extract_address); // Filter out the sender BMailAccounts accounts; BMailAccountSettings* account = accounts.AccountByID(Account()); BString sender; if (account != NULL) sender = account->ReturnAddress(); extract_address(sender); BString cc; for (int32 i = list.CountItems(); i-- > 0;) { char* address = (char*)list.RemoveItem((int32)0); // Add everything which is not the sender and not already in the // list if (sender.ICompare(address) && cc.FindFirst(address) < 0) { if (cc.Length() > 0) cc << ", "; cc << address; } free(address); } if (cc.Length() > 0) reply->SetCC(cc.String()); } else if (replyTo == B_MAIL_REPLY_TO_SENDER || ReplyTo() == NULL) reply->SetTo(From()); else reply->SetTo(ReplyTo()); // Set special "In-Reply-To:" header (used for threading) const char* messageID = fBody ? fBody->HeaderField("Message-Id") : NULL; if (messageID != NULL) reply->SetHeaderField("In-Reply-To", messageID); // quote body text reply->SetBodyTextTo(BodyText()); if (quoteStyle) reply->Body()->Quote(quoteStyle); // Set the subject (and add a "Re:" if needed) BString string = Subject(); if (string.ICompare("re:", 3) != 0) string.Prepend("Re: "); reply->SetSubject(string.String()); // set the matching outbound chain if (accountFromMail) reply->SendViaAccountFrom(this); return reply; } BEmailMessage* BEmailMessage::ForwardMessage(bool accountFromMail, bool includeAttachments) { BString header = "------ Forwarded Message: ------\n"; header << "To: " << To() << '\n'; header << "From: " << From() << '\n'; if (CC() != NULL) { // Can use CC rather than "Cc" since display only. header << "CC: " << CC() << '\n'; } header << "Subject: " << Subject() << '\n'; header << "Date: " << HeaderField("Date") << "\n\n"; if (fTextBody != NULL) header << fTextBody->Text() << '\n'; BEmailMessage *message = new BEmailMessage(); message->SetBodyTextTo(header.String()); // set the subject BString subject = Subject(); if (subject.IFindFirst("fwd") == B_ERROR && subject.IFindFirst("forward") == B_ERROR && subject.FindFirst("FW") == B_ERROR) subject << " (fwd)"; message->SetSubject(subject.String()); if (includeAttachments) { for (int32 i = 0; i < CountComponents(); i++) { BMailComponent* component = GetComponent(i); if (component == fTextBody || component == NULL) continue; //---I am ashamed to have the written the code between here and the next comment // ... and you still managed to get it wrong ;-)), axeld. // we should really move this stuff into copy constructors // or something like that BMallocIO io; component->RenderToRFC822(&io); BMailComponent* clone = component->WhatIsThis(); io.Seek(0, SEEK_SET); clone->SetToRFC822(&io, io.BufferLength(), true); message->AddComponent(clone); } } if (accountFromMail) message->SendViaAccountFrom(this); return message; } const char* BEmailMessage::To() const { return HeaderField("To"); } const char* BEmailMessage::From() const { return HeaderField("From"); } const char* BEmailMessage::ReplyTo() const { return HeaderField("Reply-To"); } const char* BEmailMessage::CC() const { return HeaderField("Cc"); // Note case of CC is "Cc" in our internal headers. } const char* BEmailMessage::Subject() const { return HeaderField("Subject"); } time_t BEmailMessage::Date() const { const char* dateField = HeaderField("Date"); if (dateField == NULL) return -1; return ParseDateWithTimeZone(dateField); } int BEmailMessage::Priority() const { int priorityNumber; const char* priorityString; /* The usual values are a number from 1 to 5, or one of three words: X-Priority: 1 and/or X-MSMail-Priority: High X-Priority: 3 and/or X-MSMail-Priority: Normal X-Priority: 5 and/or X-MSMail-Priority: Low Also plain Priority: is "normal", "urgent" or "non-urgent", see RFC 1327. */ priorityString = HeaderField("Priority"); if (priorityString == NULL) priorityString = HeaderField("X-Priority"); if (priorityString == NULL) priorityString = HeaderField("X-Msmail-Priority"); if (priorityString == NULL) return 3; priorityNumber = atoi (priorityString); if (priorityNumber != 0) { if (priorityNumber > 5) priorityNumber = 5; if (priorityNumber < 1) priorityNumber = 1; return priorityNumber; } if (strcasecmp (priorityString, "Low") == 0 || strcasecmp (priorityString, "non-urgent") == 0) return 5; if (strcasecmp (priorityString, "High") == 0 || strcasecmp (priorityString, "urgent") == 0) return 1; return 3; } void BEmailMessage::SetSubject(const char* subject, uint32 charset, mail_encoding encoding) { SetHeaderField("Subject", subject, charset, encoding); } void BEmailMessage::SetReplyTo(const char* replyTo, uint32 charset, mail_encoding encoding) { SetHeaderField("Reply-To", replyTo, charset, encoding); } void BEmailMessage::SetFrom(const char* from, uint32 charset, mail_encoding encoding) { SetHeaderField("From", from, charset, encoding); } void BEmailMessage::SetTo(const char* to, uint32 charset, mail_encoding encoding) { SetHeaderField("To", to, charset, encoding); } void BEmailMessage::SetCC(const char* cc, uint32 charset, mail_encoding encoding) { // For consistency with our header names, use Cc as the name. SetHeaderField("Cc", cc, charset, encoding); } void BEmailMessage::SetBCC(const char* bcc) { free(fBCC); fBCC = strdup(bcc); } void BEmailMessage::SetPriority(int to) { char tempString[20]; if (to < 1) to = 1; if (to > 5) to = 5; sprintf (tempString, "%d", to); SetHeaderField("X-Priority", tempString); if (to <= 2) { SetHeaderField("Priority", "urgent"); SetHeaderField("X-Msmail-Priority", "High"); } else if (to >= 4) { SetHeaderField("Priority", "non-urgent"); SetHeaderField("X-Msmail-Priority", "Low"); } else { SetHeaderField("Priority", "normal"); SetHeaderField("X-Msmail-Priority", "Normal"); } } status_t BEmailMessage::GetName(char* name, int32 maxLength) const { if (name == NULL || maxLength <= 0) return B_BAD_VALUE; if (BFile* file = dynamic_cast(fData)) { status_t status = file->ReadAttr(B_MAIL_ATTR_NAME, B_STRING_TYPE, 0, name, maxLength); name[maxLength - 1] = '\0'; return status >= 0 ? B_OK : status; } // TODO: look at From header? But usually there is // a file since only the BeMail GUI calls this. return B_ERROR; } status_t BEmailMessage::GetName(BString* name) const { char* buffer = name->LockBuffer(B_FILE_NAME_LENGTH); status_t status = GetName(buffer, B_FILE_NAME_LENGTH); name->UnlockBuffer(); return status; } void BEmailMessage::SendViaAccountFrom(BEmailMessage* message) { BString name; if (message->GetAccountName(name) < B_OK) { // just return the message with the default account return; } SendViaAccount(name); } void BEmailMessage::SendViaAccount(const char* accountName) { BMailAccounts accounts; BMailAccountSettings* account = accounts.AccountByName(accountName); if (account != NULL) SendViaAccount(account->AccountID()); } void BEmailMessage::SendViaAccount(int32 account) { fAccountID = account; BMailAccounts accounts; BMailAccountSettings* accountSettings = accounts.AccountByID(fAccountID); BString from; if (accountSettings) { from << '\"' << accountSettings->RealName() << "\" <" << accountSettings->ReturnAddress() << '>'; } SetFrom(from); } int32 BEmailMessage::Account() const { return fAccountID; } status_t BEmailMessage::GetAccountName(BString& accountName) const { BFile* file = dynamic_cast(fData); if (file == NULL) return B_ERROR; int32 accountID; size_t read = file->ReadAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0, &accountID, sizeof(int32)); if (read < sizeof(int32)) return B_ERROR; BMailAccounts accounts; BMailAccountSettings* account = accounts.AccountByID(accountID); if (account != NULL) accountName = account->Name(); else accountName = ""; return B_OK; } status_t BEmailMessage::AddComponent(BMailComponent* component) { status_t status = B_OK; if (fComponentCount == 0) fBody = component; else if (fComponentCount == 1) { BMIMEMultipartMailContainer *container = new BMIMEMultipartMailContainer( mime_boundary, mime_warning, _charSetForTextDecoding); status = container->AddComponent(fBody); if (status == B_OK) status = container->AddComponent(component); fBody = container; } else { BMIMEMultipartMailContainer* container = dynamic_cast(fBody); if (container == NULL) return B_MISMATCHED_VALUES; status = container->AddComponent(component); } if (status == B_OK) fComponentCount++; return status; } status_t BEmailMessage::RemoveComponent(BMailComponent* /*component*/) { // not yet implemented // BeMail/Enclosures.cpp:169: contains a warning about this fact return B_ERROR; } status_t BEmailMessage::RemoveComponent(int32 /*index*/) { // not yet implemented return B_ERROR; } BMailComponent* BEmailMessage::GetComponent(int32 i, bool parseNow) { if (BMIMEMultipartMailContainer* container = dynamic_cast(fBody)) return container->GetComponent(i, parseNow); if (i < fComponentCount) return fBody; return NULL; } int32 BEmailMessage::CountComponents() const { return fComponentCount; } void BEmailMessage::Attach(entry_ref* ref, bool includeAttributes) { if (includeAttributes) AddComponent(new BAttributedMailAttachment(ref)); else AddComponent(new BSimpleMailAttachment(ref)); } bool BEmailMessage::IsComponentAttachment(int32 i) { if ((i >= fComponentCount) || (fComponentCount == 0)) return false; if (fComponentCount == 1) return fBody->IsAttachment(); BMIMEMultipartMailContainer* container = dynamic_cast(fBody); if (container == NULL) return false; BMailComponent* component = container->GetComponent(i); if (component == NULL) return false; return component->IsAttachment(); } void BEmailMessage::SetBodyTextTo(const char* text) { if (fTextBody == NULL) { fTextBody = new BTextMailComponent; AddComponent(fTextBody); } fTextBody->SetText(text); } BTextMailComponent* BEmailMessage::Body() { if (fTextBody == NULL) fTextBody = _RetrieveTextBody(fBody); return fTextBody; } const char* BEmailMessage::BodyText() { if (Body() == NULL) return NULL; return fTextBody->Text(); } status_t BEmailMessage::SetBody(BTextMailComponent* body) { if (fTextBody != NULL) { return B_ERROR; // removing doesn't exist for now // RemoveComponent(fTextBody); // delete fTextBody; } fTextBody = body; AddComponent(fTextBody); return B_OK; } BTextMailComponent* BEmailMessage::_RetrieveTextBody(BMailComponent* component) { BTextMailComponent* body = dynamic_cast(component); if (body != NULL) return body; BMIMEMultipartMailContainer* container = dynamic_cast(component); if (container != NULL) { for (int32 i = 0; i < container->CountComponents(); i++) { if ((component = container->GetComponent(i)) == NULL) continue; switch (component->ComponentType()) { case B_MAIL_PLAIN_TEXT_BODY: // AttributedAttachment returns the MIME type of its // contents, so we have to use dynamic_cast here body = dynamic_cast( container->GetComponent(i)); if (body != NULL) return body; break; case B_MAIL_MULTIPART_CONTAINER: body = _RetrieveTextBody(container->GetComponent(i)); if (body != NULL) return body; break; } } } return NULL; } status_t BEmailMessage::SetToRFC822(BPositionIO* mailFile, size_t length, bool parseNow) { if (BFile* file = dynamic_cast(mailFile)) { file->ReadAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &fAccountID, sizeof(fAccountID)); } mailFile->Seek(0, SEEK_END); length = mailFile->Position(); mailFile->Seek(0, SEEK_SET); fStatus = BMailComponent::SetToRFC822(mailFile, length, parseNow); if (fStatus < B_OK) return fStatus; fBody = WhatIsThis(); mailFile->Seek(0, SEEK_SET); fStatus = fBody->SetToRFC822(mailFile, length, parseNow); if (fStatus < B_OK) return fStatus; // Move headers that we use to us, everything else to fBody const char* name; for (int32 i = 0; (name = fBody->HeaderAt(i)) != NULL; i++) { if (strcasecmp(name, "Subject") != 0 && strcasecmp(name, "To") != 0 && strcasecmp(name, "From") != 0 && strcasecmp(name, "Reply-To") != 0 && strcasecmp(name, "Cc") != 0 && strcasecmp(name, "Priority") != 0 && strcasecmp(name, "X-Priority") != 0 && strcasecmp(name, "X-Msmail-Priority") != 0 && strcasecmp(name, "Date") != 0) { RemoveHeader(name); } } fBody->RemoveHeader("Subject"); fBody->RemoveHeader("To"); fBody->RemoveHeader("From"); fBody->RemoveHeader("Reply-To"); fBody->RemoveHeader("Cc"); fBody->RemoveHeader("Priority"); fBody->RemoveHeader("X-Priority"); fBody->RemoveHeader("X-Msmail-Priority"); fBody->RemoveHeader("Date"); fComponentCount = 1; if (BMIMEMultipartMailContainer* container = dynamic_cast(fBody)) fComponentCount = container->CountComponents(); return B_OK; } status_t BEmailMessage::RenderToRFC822(BPositionIO* file) { if (fBody == NULL) return B_MAIL_INVALID_MAIL; // Do real rendering if (From() == NULL) { // set the "From:" string SendViaAccount(fAccountID); } BList recipientList; get_address_list(recipientList, To(), extract_address); get_address_list(recipientList, CC(), extract_address); get_address_list(recipientList, fBCC, extract_address); BString recipients; for (int32 i = recipientList.CountItems(); i-- > 0;) { char *address = (char *)recipientList.RemoveItem((int32)0); recipients << '<' << address << '>'; if (i) recipients << ','; free(address); } // add the date field time_t creationTime = time(NULL); { char date[128]; struct tm tm; localtime_r(&creationTime, &tm); size_t length = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S", &tm); // GMT offsets are full hours, yes, but you never know :-) snprintf(date + length, sizeof(date) - length, " %+03d%02d", tm.tm_gmtoff / 3600, (tm.tm_gmtoff / 60) % 60); SetHeaderField("Date", date); } // add a message-id // empirical evidence indicates message id must be enclosed in // angle brackets and there must be an "at" symbol in it BString messageID; messageID << "<"; messageID << system_time(); messageID << "-BeMail@"; char host[255]; if (gethostname(host, sizeof(host)) < 0 || !host[0]) strcpy(host, "zoidberg"); messageID << host; messageID << ">"; SetHeaderField("Message-Id", messageID.String()); status_t err = BMailComponent::RenderToRFC822(file); if (err < B_OK) return err; file->Seek(-2, SEEK_CUR); // Remove division between headers err = fBody->RenderToRFC822(file); if (err < B_OK) return err; // Set the message file's attributes. Do this after the rest of the file // is filled in, in case the daemon attempts to send it before it is ready // (since the daemon may send it when it sees the status attribute getting // set to "Pending"). if (BFile* attributed = dynamic_cast (file)) { BNodeInfo(attributed).SetType(B_MAIL_TYPE); attributed->WriteAttrString(B_MAIL_ATTR_RECIPIENTS,&recipients); BString attr; attr = To(); attributed->WriteAttrString(B_MAIL_ATTR_TO, &attr); attr = CC(); attributed->WriteAttrString(B_MAIL_ATTR_CC, &attr); attr = Subject(); attributed->WriteAttrString(B_MAIL_ATTR_SUBJECT, &attr); attr = ReplyTo(); attributed->WriteAttrString(B_MAIL_ATTR_REPLY, &attr); attr = From(); attributed->WriteAttrString(B_MAIL_ATTR_FROM, &attr); if (Priority() != 3 /* Normal is 3 */) { sprintf(attr.LockBuffer(40), "%d", Priority()); attr.UnlockBuffer(-1); attributed->WriteAttrString(B_MAIL_ATTR_PRIORITY, &attr); } attr = "Pending"; attributed->WriteAttrString(B_MAIL_ATTR_STATUS, &attr); attr = "1.0"; attributed->WriteAttrString(B_MAIL_ATTR_MIME, &attr); attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT, B_INT32_TYPE, 0, &fAccountID, sizeof(int32)); attributed->WriteAttr(B_MAIL_ATTR_WHEN, B_TIME_TYPE, 0, &creationTime, sizeof(int32)); int32 flags = B_MAIL_PENDING | B_MAIL_SAVE; attributed->WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32)); attributed->WriteAttr(B_MAIL_ATTR_ACCOUNT_ID, B_INT32_TYPE, 0, &fAccountID, sizeof(int32)); } return B_OK; } status_t BEmailMessage::RenderTo(BDirectory* dir, BEntry* msg) { time_t currentTime; char numericDateString[40]; struct tm timeFields; BString worker; // Generate a file name for the outgoing message. See also // FolderFilter::ProcessMailMessage which does something similar for // incoming messages. BString name = Subject(); SubjectToThread(name); // Extract the core subject words. if (name.Length() <= 0) name = "No Subject"; if (name[0] == '.') { // Avoid hidden files, starting with a dot. name.Prepend("_"); } // Convert the date into a year-month-day fixed digit width format, so that // sorting by file name will give all the messages with the same subject in // order of date. time (¤tTime); localtime_r (¤tTime, &timeFields); sprintf (numericDateString, "%04d%02d%02d%02d%02d%02d", timeFields.tm_year + 1900, timeFields.tm_mon + 1, timeFields.tm_mday, timeFields.tm_hour, timeFields.tm_min, timeFields.tm_sec); name << " " << numericDateString; worker = From(); extract_address_name(worker); name << " " << worker; name.Truncate(222); // reserve space for the uniquer // Get rid of annoying characters which are hard to use in the shell. name.ReplaceAll('/','_'); name.ReplaceAll('\'','_'); name.ReplaceAll('"','_'); name.ReplaceAll('!','_'); name.ReplaceAll('<','_'); name.ReplaceAll('>','_'); // Remove multiple spaces. while (name.FindFirst(" ") >= 0) name.Replace(" ", " ", 1024); int32 uniquer = time(NULL); worker = name; int32 tries = 30; bool exists; while ((exists = dir->Contains(worker.String())) && --tries > 0) { srand(rand()); uniquer += (rand() >> 16) - 16384; worker = name; worker << ' ' << uniquer; } if (exists) printf("could not create mail! (should be: %s)\n", worker.String()); BFile file; status_t status = dir->CreateFile(worker.String(), &file); if (status != B_OK) return status; if (msg != NULL) msg->SetTo(dir,worker.String()); return RenderToRFC822(&file); } status_t BEmailMessage::Send(bool sendNow) { BMailAccounts accounts; BMailAccountSettings* account = accounts.AccountByID(fAccountID); if (account == NULL || !account->HasOutbound()) { account = accounts.AccountByID( BMailSettings().DefaultOutboundAccount()); if (!account) return B_ERROR; SendViaAccount(account->AccountID()); } BString path; if (account->OutboundSettings().FindString("path", &path) != B_OK) { BPath defaultMailOutPath; if (find_directory(B_USER_DIRECTORY, &defaultMailOutPath) != B_OK || defaultMailOutPath.Append("mail/out") != B_OK) path = "/boot/home/mail/out"; else path = defaultMailOutPath.Path(); } create_directory(path.String(), 0777); BDirectory directory(path.String()); BEntry message; status_t status = RenderTo(&directory, &message); if (status >= B_OK && sendNow) { // TODO: check whether or not the internet connection is available BMessenger daemon(B_MAIL_DAEMON_SIGNATURE); if (!daemon.IsValid()) return B_MAIL_NO_DAEMON; BMessage msg(kMsgSendMessages); msg.AddInt32("account", fAccountID); BPath path; message.GetPath(&path); msg.AddString("message_path", path.Path()); daemon.SendMessage(&msg); } return status; } void BEmailMessage::_ReservedMessage1() {} void BEmailMessage::_ReservedMessage2() {} void BEmailMessage::_ReservedMessage3() {}