#include "HaikuMailFormatFilter.h"
#include <ctype.h>
#include <Directory.h>
#include <E-mail.h>
#include <NodeInfo.h>
#include <mail_util.h>
struct mail_header_field {
const char* rfc_name;
const char* attr_name;
type_code attr_type;
};
static const mail_header_field gDefaultFields[] = {
{ "To", B_MAIL_ATTR_TO, B_STRING_TYPE },
{ "From", B_MAIL_ATTR_FROM, B_STRING_TYPE },
{ "Cc", B_MAIL_ATTR_CC, B_STRING_TYPE },
{ "Date", B_MAIL_ATTR_WHEN, B_TIME_TYPE },
{ "Delivery-Date", B_MAIL_ATTR_WHEN, B_TIME_TYPE },
{ "Reply-To", B_MAIL_ATTR_REPLY, B_STRING_TYPE },
{ "Subject", B_MAIL_ATTR_SUBJECT, B_STRING_TYPE },
{ "X-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
{ "Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
{ "X-Msmail-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
{ "Mime-Version", B_MAIL_ATTR_MIME, B_STRING_TYPE },
{ "STATUS", B_MAIL_ATTR_STATUS, B_STRING_TYPE },
{ "THREAD", B_MAIL_ATTR_THREAD, B_STRING_TYPE },
{ "NAME", B_MAIL_ATTR_NAME, B_STRING_TYPE },
{ NULL, NULL, 0 }
};
void
sanitize_white_space(BString& string)
{
char* buffer = string.LockBuffer(string.Length() + 1);
if (buffer == NULL)
return;
int32 count = string.Length();
int32 spaces = 0;
for (int32 i = 0; buffer[i] != '\0'; i++, count--) {
if (isspace(buffer[i])) {
buffer[i] = ' ';
spaces++;
} else {
if (spaces > 1)
memmove(buffer + i + 1 - spaces, buffer + i, count + 1);
spaces = 0;
}
}
string.UnlockBuffer();
}
HaikuMailFormatFilter::HaikuMailFormatFilter(BMailProtocol& protocol,
const BMailAccountSettings& settings)
:
BMailFilter(protocol, NULL),
fAccountID(settings.AccountID()),
fAccountName(settings.Name())
{
const BMessage& outboundSettings = settings.OutboundSettings();
outboundSettings.FindString("destination", &fOutboundDirectory);
}
BString
HaikuMailFormatFilter::DescriptiveName() const
{
return "built-in";
}
BMailFilterAction
HaikuMailFormatFilter::HeaderFetched(entry_ref& ref, BFile& file,
BMessage& attributes)
{
file.Seek(0, SEEK_SET);
attributes.AddInt32(B_MAIL_ATTR_ACCOUNT_ID, fAccountID);
attributes.AddString(B_MAIL_ATTR_ACCOUNT, fAccountName);
BString header;
off_t size;
if (file.GetSize(&size) == B_OK) {
char* buffer = header.LockBuffer(size);
if (buffer == NULL)
return B_NO_MEMORY;
ssize_t bytesRead = file.Read(buffer, size);
if (bytesRead < 0)
return bytesRead;
if (bytesRead != size)
return B_IO_ERROR;
header.UnlockBuffer(size);
}
for (int i = 0; gDefaultFields[i].rfc_name; ++i) {
BString target;
status_t status = extract_from_header(header,
gDefaultFields[i].rfc_name, target);
if (status != B_OK)
continue;
switch (gDefaultFields[i].attr_type){
case B_STRING_TYPE:
sanitize_white_space(target);
attributes.AddString(gDefaultFields[i].attr_name, target);
break;
case B_TIME_TYPE:
{
time_t when;
when = ParseDateWithTimeZone(target);
if (when == -1)
when = time(NULL);
attributes.AddData(B_MAIL_ATTR_WHEN, B_TIME_TYPE, &when,
sizeof(when));
break;
}
}
}
BString senderName = _ExtractName(attributes.FindString(B_MAIL_ATTR_FROM));
attributes.AddString(B_MAIL_ATTR_NAME, senderName);
BString name = attributes.FindString(B_MAIL_ATTR_SUBJECT);
SubjectToThread(name);
if (name.Length() <= 0)
name = "No Subject";
attributes.AddString(B_MAIL_ATTR_THREAD, name);
time_t dateAsTime = 0;
const time_t* datePntr;
ssize_t dateSize;
char numericDateString[40];
struct tm timeFields;
if (attributes.FindData(B_MAIL_ATTR_WHEN, B_TIME_TYPE,
(const void**)&datePntr, &dateSize) == B_OK)
dateAsTime = *datePntr;
localtime_r(&dateAsTime, &timeFields);
snprintf(numericDateString, sizeof(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;
BString workerName = attributes.FindString(B_MAIL_ATTR_FROM);
extract_address_name(workerName);
name << " " << workerName;
name.Truncate(222);
name.ReplaceAll('/', '_');
name.ReplaceAll('\'', '_');
name.ReplaceAll('"', '_');
name.ReplaceAll('!', '_');
name.ReplaceAll('<', '_');
name.ReplaceAll('>', '_');
_RemoveExtraWhitespace(name);
_RemoveLeadingDots(name);
if (!attributes.HasString(B_MAIL_ATTR_STATUS))
attributes.AddString(B_MAIL_ATTR_STATUS, "New");
_SetType(attributes, B_PARTIAL_MAIL_TYPE);
ref.set_name(name.String());
return B_MOVE_MAIL_ACTION;
}
void
HaikuMailFormatFilter::BodyFetched(const entry_ref& ref, BFile& file,
BMessage& attributes)
{
_SetType(attributes, B_MAIL_TYPE);
}
void
HaikuMailFormatFilter::MessageSent(const entry_ref& ref, BFile& file)
{
mail_flags flags = B_MAIL_SENT;
file.WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32));
file.WriteAttr(B_MAIL_ATTR_STATUS, B_STRING_TYPE, 0, "Sent", 5);
if (!fOutboundDirectory.IsEmpty()) {
create_directory(fOutboundDirectory, 755);
BDirectory dir(fOutboundDirectory);
BEntry entry(&ref);
entry.MoveTo(&dir);
}
}
void
HaikuMailFormatFilter::_RemoveExtraWhitespace(BString& name)
{
int spaces = 0;
for (int i = 0; i <= name.Length();) {
if (i < name.Length() && isspace(name.ByteAt(i))) {
spaces++;
i++;
} else if (spaces > 0) {
int remove = spaces - 1;
if (i == remove + 1 || i == name.Length())
remove++;
else
name.SetByteAt(i - spaces, ' ');
name.Remove(i - remove, remove);
i -= remove;
spaces = 0;
} else
i++;
}
}
void
HaikuMailFormatFilter::_RemoveLeadingDots(BString& name)
{
int dots = 0;
while (dots < name.Length() && name.ByteAt(dots) == '.')
dots++;
if (dots > 0)
name.Remove(0, dots);
}
BString
HaikuMailFormatFilter::_ExtractName(const BString& from)
{
BString name;
int32 emailStart = from.FindFirst("<");
if (emailStart < 0) {
name = from;
return name.Trim();
}
from.CopyInto(name, 0, emailStart);
name.Trim();
if (name.Length() >= 2) {
if (name[name.Length() - 1] == '\"')
name.Truncate(name.Length() - 1, true);
if (name[0] == '\"')
name.Remove(0, 1);
name.Trim();
}
if (name != "")
return name;
name = from;
name.Remove(0, emailStart + 1);
name.Trim();
if (name.Length() < 1)
return from;
if (name[name.Length() - 1] == '>')
name.Truncate(name.Length() - 1, true);
name.Trim();
return name;
}
status_t
HaikuMailFormatFilter::_SetType(BMessage& attributes, const char* mimeType)
{
return attributes.SetData("BEOS:TYPE", B_MIME_STRING_TYPE, mimeType,
strlen(mimeType) + 1, false);
}