vfs: check userland buffers before reading them.
[haiku.git] / src / kits / mail / HaikuMailFormatFilter.cpp
blobb16893858b3296467d7e3bca42e745e54dcfc188
1 /*
2 * Copyright 2011-2013, Haiku, Inc. All rights reserved.
3 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
4 * Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved.
6 * Distributed under the terms of the MIT License.
7 */
10 #include "HaikuMailFormatFilter.h"
12 #include <ctype.h>
14 #include <Directory.h>
15 #include <E-mail.h>
16 #include <NodeInfo.h>
18 #include <mail_util.h>
21 struct mail_header_field {
22 const char* rfc_name;
23 const char* attr_name;
24 type_code attr_type;
25 // currently either B_STRING_TYPE and B_TIME_TYPE
29 static const mail_header_field gDefaultFields[] = {
30 { "To", B_MAIL_ATTR_TO, B_STRING_TYPE },
31 { "From", B_MAIL_ATTR_FROM, B_STRING_TYPE },
32 { "Cc", B_MAIL_ATTR_CC, B_STRING_TYPE },
33 { "Date", B_MAIL_ATTR_WHEN, B_TIME_TYPE },
34 { "Delivery-Date", B_MAIL_ATTR_WHEN, B_TIME_TYPE },
35 { "Reply-To", B_MAIL_ATTR_REPLY, B_STRING_TYPE },
36 { "Subject", B_MAIL_ATTR_SUBJECT, B_STRING_TYPE },
37 { "X-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
38 // Priorities with preferred
39 { "Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
40 // one first - the numeric
41 { "X-Msmail-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE },
42 // one (has more levels).
43 { "Mime-Version", B_MAIL_ATTR_MIME, B_STRING_TYPE },
44 { "STATUS", B_MAIL_ATTR_STATUS, B_STRING_TYPE },
45 { "THREAD", B_MAIL_ATTR_THREAD, B_STRING_TYPE },
46 { "NAME", B_MAIL_ATTR_NAME, B_STRING_TYPE },
47 { NULL, NULL, 0 }
51 //! Replaces tabs and other white space with spaces, compresses spaces.
52 void
53 sanitize_white_space(BString& string)
55 char* buffer = string.LockBuffer(string.Length() + 1);
56 if (buffer == NULL)
57 return;
59 int32 count = string.Length();
60 int32 spaces = 0;
61 for (int32 i = 0; buffer[i] != '\0'; i++, count--) {
62 if (isspace(buffer[i])) {
63 buffer[i] = ' ';
64 spaces++;
65 } else {
66 if (spaces > 1)
67 memmove(buffer + i + 1 - spaces, buffer + i, count + 1);
68 spaces = 0;
72 string.UnlockBuffer();
76 // #pragma mark -
79 HaikuMailFormatFilter::HaikuMailFormatFilter(BMailProtocol& protocol,
80 const BMailAccountSettings& settings)
82 BMailFilter(protocol, NULL),
83 fAccountID(settings.AccountID()),
84 fAccountName(settings.Name())
86 const BMessage& outboundSettings = settings.OutboundSettings();
87 outboundSettings.FindString("destination", &fOutboundDirectory);
91 BString
92 HaikuMailFormatFilter::DescriptiveName() const
94 // This will not be called by the UI; no need to translate it
95 return "built-in";
99 BMailFilterAction
100 HaikuMailFormatFilter::HeaderFetched(entry_ref& ref, BFile& file,
101 BMessage& attributes)
103 file.Seek(0, SEEK_SET);
105 // TODO: attributes.AddInt32(B_MAIL_ATTR_CONTENT, length);
106 attributes.AddInt32(B_MAIL_ATTR_ACCOUNT_ID, fAccountID);
107 attributes.AddString(B_MAIL_ATTR_ACCOUNT, fAccountName);
109 BString header;
110 off_t size;
111 if (file.GetSize(&size) == B_OK) {
112 char* buffer = header.LockBuffer(size);
113 if (buffer == NULL)
114 return B_NO_MEMORY;
116 ssize_t bytesRead = file.Read(buffer, size);
117 if (bytesRead < 0)
118 return bytesRead;
119 if (bytesRead != size)
120 return B_IO_ERROR;
122 header.UnlockBuffer(size);
125 for (int i = 0; gDefaultFields[i].rfc_name; ++i) {
126 BString target;
127 status_t status = extract_from_header(header,
128 gDefaultFields[i].rfc_name, target);
129 if (status != B_OK)
130 continue;
132 switch (gDefaultFields[i].attr_type){
133 case B_STRING_TYPE:
134 sanitize_white_space(target);
135 attributes.AddString(gDefaultFields[i].attr_name, target);
136 break;
138 case B_TIME_TYPE:
140 time_t when;
141 when = ParseDateWithTimeZone(target);
142 if (when == -1)
143 when = time(NULL); // Use current time if it's undecodable.
144 attributes.AddData(B_MAIL_ATTR_WHEN, B_TIME_TYPE, &when,
145 sizeof(when));
146 break;
151 BString senderName = _ExtractName(attributes.FindString(B_MAIL_ATTR_FROM));
152 attributes.AddString(B_MAIL_ATTR_NAME, senderName);
154 // Generate a file name for the incoming message. See also
155 // Message::RenderTo which does a similar thing for outgoing messages.
156 BString name = attributes.FindString(B_MAIL_ATTR_SUBJECT);
157 SubjectToThread(name); // Extract the core subject words.
158 if (name.Length() <= 0)
159 name = "No Subject";
160 attributes.AddString(B_MAIL_ATTR_THREAD, name);
162 // Convert the date into a year-month-day fixed digit width format, so that
163 // sorting by file name will give all the messages with the same subject in
164 // order of date.
165 time_t dateAsTime = 0;
166 const time_t* datePntr;
167 ssize_t dateSize;
168 char numericDateString[40];
169 struct tm timeFields;
171 if (attributes.FindData(B_MAIL_ATTR_WHEN, B_TIME_TYPE,
172 (const void**)&datePntr, &dateSize) == B_OK)
173 dateAsTime = *datePntr;
174 localtime_r(&dateAsTime, &timeFields);
175 snprintf(numericDateString, sizeof(numericDateString),
176 "%04d%02d%02d%02d%02d%02d",
177 timeFields.tm_year + 1900, timeFields.tm_mon + 1, timeFields.tm_mday,
178 timeFields.tm_hour, timeFields.tm_min, timeFields.tm_sec);
179 name << " " << numericDateString;
181 BString workerName = attributes.FindString(B_MAIL_ATTR_FROM);
182 extract_address_name(workerName);
183 name << " " << workerName;
185 name.Truncate(222); // reserve space for the unique number
187 // Get rid of annoying characters which are hard to use in the shell.
188 name.ReplaceAll('/', '_');
189 name.ReplaceAll('\'', '_');
190 name.ReplaceAll('"', '_');
191 name.ReplaceAll('!', '_');
192 name.ReplaceAll('<', '_');
193 name.ReplaceAll('>', '_');
194 _RemoveExtraWhitespace(name);
195 _RemoveLeadingDots(name);
196 // Avoid files starting with a dot.
198 if (!attributes.HasString(B_MAIL_ATTR_STATUS))
199 attributes.AddString(B_MAIL_ATTR_STATUS, "New");
201 _SetType(attributes, B_PARTIAL_MAIL_TYPE);
203 ref.set_name(name.String());
205 return B_MOVE_MAIL_ACTION;
209 void
210 HaikuMailFormatFilter::BodyFetched(const entry_ref& ref, BFile& file,
211 BMessage& attributes)
213 _SetType(attributes, B_MAIL_TYPE);
217 void
218 HaikuMailFormatFilter::MessageSent(const entry_ref& ref, BFile& file)
220 mail_flags flags = B_MAIL_SENT;
221 file.WriteAttr(B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0, &flags, sizeof(int32));
222 file.WriteAttr(B_MAIL_ATTR_STATUS, B_STRING_TYPE, 0, "Sent", 5);
224 if (!fOutboundDirectory.IsEmpty()) {
225 create_directory(fOutboundDirectory, 755);
226 BDirectory dir(fOutboundDirectory);
227 // TODO:
228 // fMailProtocol.Looper()->TriggerFileMove(ref, dir);
229 BEntry entry(&ref);
230 entry.MoveTo(&dir);
231 // TODO: report error (via BMailProtocol::MailNotifier())
236 void
237 HaikuMailFormatFilter::_RemoveExtraWhitespace(BString& name)
239 int spaces = 0;
240 for (int i = 0; i <= name.Length();) {
241 if (i < name.Length() && isspace(name.ByteAt(i))) {
242 spaces++;
243 i++;
244 } else if (spaces > 0) {
245 int remove = spaces - 1;
246 // Also remove leading and trailing spaces
247 if (i == remove + 1 || i == name.Length())
248 remove++;
249 else
250 name.SetByteAt(i - spaces, ' ');
251 name.Remove(i - remove, remove);
252 i -= remove;
253 spaces = 0;
254 } else
255 i++;
260 void
261 HaikuMailFormatFilter::_RemoveLeadingDots(BString& name)
263 int dots = 0;
264 while (dots < name.Length() && name.ByteAt(dots) == '.')
265 dots++;
267 if (dots > 0)
268 name.Remove(0, dots);
272 BString
273 HaikuMailFormatFilter::_ExtractName(const BString& from)
275 // extract name from something like "name" <email@domain.com>
276 // if name is empty return the mail address without "<>"
278 BString name;
279 int32 emailStart = from.FindFirst("<");
280 if (emailStart < 0) {
281 name = from;
282 return name.Trim();
285 from.CopyInto(name, 0, emailStart);
286 name.Trim();
287 if (name.Length() >= 2) {
288 if (name[name.Length() - 1] == '\"')
289 name.Truncate(name.Length() - 1, true);
290 if (name[0] == '\"')
291 name.Remove(0, 1);
292 name.Trim();
294 if (name != "")
295 return name;
297 // empty name extract email address
298 name = from;
299 name.Remove(0, emailStart + 1);
300 name.Trim();
301 if (name.Length() < 1)
302 return from;
303 if (name[name.Length() - 1] == '>')
304 name.Truncate(name.Length() - 1, true);
305 name.Trim();
306 return name;
310 status_t
311 HaikuMailFormatFilter::_SetType(BMessage& attributes, const char* mimeType)
313 return attributes.SetData("BEOS:TYPE", B_MIME_STRING_TYPE, mimeType,
314 strlen(mimeType) + 1, false);