Assorted whitespace cleanup and typo fixes.
[haiku.git] / src / kits / mail / MailContainer.cpp
blobbc8e2326c2aa49a35f9017b7f1df61b42fdbb2b1
1 /* Container - message part container class
2 **
3 ** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
4 */
7 #include <String.h>
8 #include <List.h>
9 #include <Mime.h>
11 #include <stdlib.h>
12 #include <strings.h>
13 #include <unistd.h>
15 class _EXPORT BMIMEMultipartMailContainer;
17 #include <MailContainer.h>
18 #include <MailAttachment.h>
20 typedef struct message_part {
21 message_part(off_t start, off_t end) { this->start = start; this->end = end; }
23 // Offset where the part starts (includes MIME sub-headers but not the
24 // boundary line) in the message file.
25 int32 start;
27 // Offset just past the last byte of data, so total length == end - start.
28 // Note that the CRLF that starts the next boundary isn't included in the
29 // data, the end points at the start of the next CRLF+Boundary. This can
30 // lead to weird things like the blank line ending the subheader being the
31 // same as the boundary starting CRLF. So if you have something malformed
32 // like this:
33 // ------=_NextPart_005_0040_ENBYSXVW.VACTSCVC
34 // Content-Type: text/plain; charset="ISO-8859-1"
36 // ------=_NextPart_005_0040_ENBYSXVW.VACTSCVC
37 // If you subtract the header length (which includes the blank line) from
38 // the MIME part total length (which doesn't include the blank line - it's
39 // part of the next boundary), you get -2.
40 int32 end;
41 } message_part;
44 BMIMEMultipartMailContainer::BMIMEMultipartMailContainer(
45 const char *boundary,
46 const char *this_is_an_MIME_message_text,
47 uint32 defaultCharSet)
49 BMailContainer (defaultCharSet),
50 _boundary(NULL),
51 _MIME_message_warning(this_is_an_MIME_message_text),
52 _io_data(NULL)
54 // Definition of the MIME version in the mail header should be enough
55 SetHeaderField("MIME-Version","1.0");
56 SetHeaderField("Content-Type","multipart/mixed");
57 SetBoundary(boundary);
60 /*BMIMEMultipartMailContainer::BMIMEMultipartMailContainer(BMIMEMultipartMailContainer &copy) :
61 BMailComponent(copy),
62 _boundary(copy._boundary),
63 _MIME_message_warning(copy._MIME_message_warning),
64 _io_data(copy._io_data) {
65 AddHeaderField("MIME-Version","1.0");
66 AddHeaderField("Content-Type","multipart/mixed");
67 SetBoundary(boundary);
68 }*/
71 BMIMEMultipartMailContainer::~BMIMEMultipartMailContainer() {
72 for (int32 i = 0; i < _components_in_raw.CountItems(); i++)
73 delete (message_part *)_components_in_raw.ItemAt(i);
75 for (int32 i = 0; i < _components_in_code.CountItems(); i++)
76 delete (BMailComponent *)_components_in_code.ItemAt(i);
78 free((void *)_boundary);
82 void BMIMEMultipartMailContainer::SetBoundary(const char *boundary) {
83 free ((void *) _boundary);
84 _boundary = NULL;
85 if (boundary != NULL)
86 _boundary = strdup(boundary);
88 BMessage structured;
89 HeaderField("Content-Type",&structured);
91 if (_boundary == NULL)
92 structured.RemoveName("boundary");
93 else if (structured.ReplaceString("boundary",_boundary) != B_OK)
94 structured.AddString("boundary",_boundary);
96 SetHeaderField("Content-Type",&structured);
100 void BMIMEMultipartMailContainer::SetThisIsAnMIMEMessageText(const char *text) {
101 _MIME_message_warning = text;
105 status_t BMIMEMultipartMailContainer::AddComponent(BMailComponent *component) {
106 if (!_components_in_code.AddItem(component))
107 return B_ERROR;
108 if (_components_in_raw.AddItem(NULL))
109 return B_OK;
111 _components_in_code.RemoveItem(component);
112 return B_ERROR;
116 BMailComponent *BMIMEMultipartMailContainer::GetComponent(int32 index, bool parse_now) {
117 if (index >= CountComponents())
118 return NULL;
120 if (BMailComponent *component = (BMailComponent *)_components_in_code.ItemAt(index))
121 return component; //--- Handle easy case
123 message_part *part = (message_part *)(_components_in_raw.ItemAt(index));
124 if (part == NULL)
125 return NULL;
127 _io_data->Seek(part->start,SEEK_SET);
129 BMailComponent component (_charSetForTextDecoding);
130 if (component.SetToRFC822(_io_data,part->end - part->start) < B_OK)
131 return NULL;
133 BMailComponent *piece = component.WhatIsThis();
135 /* Debug code
136 _io_data->Seek(part->start,SEEK_SET);
137 char *data = new char[part->end - part->start + 1];
138 _io_data->Read(data,part->end - part->start);
139 data[part->end - part->start] = 0;
140 puts((char *)(data));
141 printf("Instantiating from %d to %d (%d octets)\n",part->start, part->end, part->end - part->start);
143 _io_data->Seek(part->start,SEEK_SET);
144 if (piece->SetToRFC822(_io_data,part->end - part->start, parse_now) < B_OK)
146 delete piece;
147 return NULL;
149 _components_in_code.ReplaceItem(index,piece);
151 return piece;
155 int32
156 BMIMEMultipartMailContainer::CountComponents() const
158 return _components_in_code.CountItems();
162 status_t
163 BMIMEMultipartMailContainer::RemoveComponent(BMailComponent *component)
165 if (component == NULL)
166 return B_BAD_VALUE;
168 int32 index = _components_in_code.IndexOf(component);
169 if (component == NULL)
170 return B_ENTRY_NOT_FOUND;
172 delete (BMailComponent *)_components_in_code.RemoveItem(index);
173 delete (message_part *)_components_in_raw.RemoveItem(index);
175 return B_OK;
179 status_t
180 BMIMEMultipartMailContainer::RemoveComponent(int32 index)
182 if (index >= CountComponents())
183 return B_BAD_INDEX;
185 delete (BMailComponent *)_components_in_code.RemoveItem(index);
186 delete (message_part *)_components_in_raw.RemoveItem(index);
188 return B_OK;
192 status_t BMIMEMultipartMailContainer::GetDecodedData(BPositionIO *)
194 return B_BAD_TYPE; //------We don't play dat
198 status_t BMIMEMultipartMailContainer::SetDecodedData(BPositionIO *) {
199 return B_BAD_TYPE; //------We don't play dat
203 status_t BMIMEMultipartMailContainer::SetToRFC822(BPositionIO *data, size_t length, bool copy_data)
205 typedef enum LookingForEnum {
206 FIRST_NEWLINE,
207 INITIAL_DASHES,
208 BOUNDARY_BODY,
209 LAST_NEWLINE,
210 MAX_LOOKING_STATES
211 } LookingFor;
213 ssize_t amountRead;
214 ssize_t amountToRead;
215 ssize_t boundaryLength;
216 char buffer [4096];
217 ssize_t bufferIndex;
218 off_t bufferOffset;
219 ssize_t bufferSize;
220 BMessage content_type;
221 const char *content_type_string;
222 bool finalBoundary = false;
223 bool finalComponentCompleted = false;
224 int i;
225 off_t lastBoundaryOffset;
226 LookingFor state;
227 off_t startOfBoundaryOffset;
228 off_t topLevelEnd;
229 off_t topLevelStart;
231 // Clear out old components. Maybe make a MakeEmpty method?
233 for (i = _components_in_code.CountItems(); i-- > 0;)
234 delete (BMailComponent *)_components_in_code.RemoveItem(i);
236 for (i = _components_in_raw.CountItems(); i-- > 0;)
237 delete (message_part *)_components_in_raw.RemoveItem(i);
239 // Start by reading the headers and getting the boundary string.
241 _io_data = data;
242 topLevelStart = data->Position();
243 topLevelEnd = topLevelStart + length;
245 BMailComponent::SetToRFC822(data,length);
247 HeaderField("Content-Type",&content_type);
248 content_type_string = content_type.FindString("unlabeled");
249 if (content_type_string == NULL ||
250 strncasecmp(content_type_string,"multipart",9) != 0)
251 return B_BAD_TYPE;
253 if (!content_type.HasString("boundary"))
254 return B_BAD_TYPE;
255 free ((void *) _boundary);
256 _boundary = strdup(content_type.FindString("boundary"));
257 boundaryLength = strlen(_boundary);
258 if (boundaryLength > (ssize_t) sizeof (buffer) / 2)
259 return B_BAD_TYPE; // Boundary is way too long, should be max 70 chars.
261 // Find container parts by scanning through the given portion of the file
262 // for the boundary marker lines. The stuff between the header and the
263 // first boundary is ignored, the same as the stuff after the last
264 // boundary. The rest get stored away as our sub-components. See RFC2046
265 // section 5.1 for details.
267 bufferOffset = data->Position(); // File offset of the start of the buffer.
268 bufferIndex = 0; // Current position we are examining in the buffer.
269 bufferSize = 0; // Amount of data actually in the buffer, not including NUL.
270 startOfBoundaryOffset = -1;
271 lastBoundaryOffset = -1;
272 state = INITIAL_DASHES; // Starting just after a new line so don't search for it.
273 while (((bufferOffset + bufferIndex < topLevelEnd)
274 || (state == LAST_NEWLINE /* No EOF test in LAST_NEWLINE state */))
275 && !finalComponentCompleted)
277 // Refill the buffer if the remaining amount of data is less than a
278 // boundary's worth, plus four dashes and two CRLFs.
279 if (bufferSize - bufferIndex < boundaryLength + 8)
281 // Shuffle the remaining bit of data in the buffer over to the front.
282 if (bufferSize - bufferIndex > 0)
283 memmove (buffer, buffer + bufferIndex, bufferSize - bufferIndex);
284 bufferOffset += bufferIndex;
285 bufferSize = bufferSize - bufferIndex;
286 bufferIndex = 0;
288 // Fill up the rest of the buffer with more data. Also leave space
289 // for a NUL byte just past the last data in the buffer so that
290 // simple string searches won't go off past the end of the data.
291 amountToRead = topLevelEnd - (bufferOffset + bufferSize);
292 if (amountToRead > (ssize_t) sizeof (buffer) - 1 - bufferSize)
293 amountToRead = sizeof (buffer) - 1 - bufferSize;
294 if (amountToRead > 0) {
295 amountRead = data->Read (buffer + bufferSize, amountToRead);
296 if (amountRead < 0)
297 return amountRead;
298 bufferSize += amountRead;
300 buffer [bufferSize] = 0; // Add an end of string NUL byte.
303 // Search for whatever parts of the boundary we are currently looking
304 // for in the buffer. It starts with a newline (officially CRLF but we
305 // also accept just LF for off-line e-mail files), followed by two
306 // hyphens or dashes "--", followed by the unique boundary string
307 // specified earlier in the header, followed by two dashes "--" for the
308 // final boundary (or zero dashes for intermediate boundaries),
309 // followed by white space (possibly including header style comments in
310 // brackets), and then a newline.
312 switch (state) {
313 case FIRST_NEWLINE:
314 // The newline before the boundary is considered to be owned by
315 // the boundary, not part of the previous MIME component.
316 startOfBoundaryOffset = bufferOffset + bufferIndex;
317 if (buffer[bufferIndex] == '\r' && buffer[bufferIndex + 1] == '\n') {
318 bufferIndex += 2;
319 state = INITIAL_DASHES;
320 } else if (buffer[bufferIndex] == '\n') {
321 bufferIndex += 1;
322 state = INITIAL_DASHES;
323 } else
324 bufferIndex++;
325 break;
327 case INITIAL_DASHES:
328 if (buffer[bufferIndex] == '-' && buffer[bufferIndex + 1] == '-') {
329 bufferIndex += 2;
330 state = BOUNDARY_BODY;
331 } else
332 state = FIRST_NEWLINE;
333 break;
335 case BOUNDARY_BODY:
336 if (strncmp (buffer + bufferIndex, _boundary, boundaryLength) != 0) {
337 state = FIRST_NEWLINE;
338 break;
340 bufferIndex += boundaryLength;
341 finalBoundary = false;
342 if (buffer[bufferIndex] == '-' && buffer[bufferIndex + 1] == '-') {
343 bufferIndex += 2;
344 finalBoundary = true;
346 state = LAST_NEWLINE;
347 break;
349 case LAST_NEWLINE:
350 // Just keep on scanning until the next new line or end of file.
351 if (buffer[bufferIndex] == '\r' && buffer[bufferIndex + 1] == '\n')
352 bufferIndex += 2;
353 else if (buffer[bufferIndex] == '\n')
354 bufferIndex += 1;
355 else if (buffer[bufferIndex] != 0 /* End of file is like a newline */) {
356 // Not a new line or end of file, just skip over
357 // everything. White space or not, we don't really care.
358 bufferIndex += 1;
359 break;
361 // Got to the end of the boundary line and maybe now have
362 // another component to add.
363 if (lastBoundaryOffset >= 0) {
364 _components_in_raw.AddItem (new message_part (lastBoundaryOffset, startOfBoundaryOffset));
365 _components_in_code.AddItem (NULL);
367 // Next component's header starts just after the boundary line.
368 lastBoundaryOffset = bufferOffset + bufferIndex;
369 if (finalBoundary)
370 finalComponentCompleted = true;
371 state = FIRST_NEWLINE;
372 break;
374 default: // Should not happen.
375 state = FIRST_NEWLINE;
379 // Some bad MIME encodings (usually spam, or damaged files) don't put on
380 // the trailing boundary. Dump whatever is remaining into a final
381 // component if there wasn't a trailing boundary and there is some data
382 // remaining.
384 if (!finalComponentCompleted
385 && lastBoundaryOffset >= 0 && lastBoundaryOffset < topLevelEnd) {
386 _components_in_raw.AddItem (new message_part (lastBoundaryOffset, topLevelEnd));
387 _components_in_code.AddItem (NULL);
390 // If requested, actually read the data inside each component, otherwise
391 // only the positions in the BPositionIO are recorded.
393 if (copy_data) {
394 for (i = 0; GetComponent(i, true /* parse_now */) != NULL; i++) {}
397 data->Seek (topLevelEnd, SEEK_SET);
398 return B_OK;
402 status_t BMIMEMultipartMailContainer::RenderToRFC822(BPositionIO *render_to) {
403 BMailComponent::RenderToRFC822(render_to);
405 BString delimiter;
406 delimiter << "\r\n--" << _boundary << "\r\n";
408 if (_MIME_message_warning != NULL) {
409 render_to->Write(_MIME_message_warning,strlen(_MIME_message_warning));
410 render_to->Write("\r\n",2);
413 for (int32 i = 0; i < _components_in_code.CountItems() /* both have equal length, so pick one at random */; i++) {
414 render_to->Write(delimiter.String(),delimiter.Length());
415 if (_components_in_code.ItemAt(i) != NULL) { //---- _components_in_code has precedence
417 BMailComponent *code = (BMailComponent *)_components_in_code.ItemAt(i);
418 status_t status = code->RenderToRFC822(render_to); //----Easy enough
419 if (status < B_OK)
420 return status;
421 } else {
422 // copy message contents
424 uint8 buffer[1024];
425 ssize_t amountWritten, length;
426 message_part *part = (message_part *)_components_in_raw.ItemAt(i);
428 for (off_t begin = part->start; begin < part->end;
429 begin += sizeof(buffer)) {
430 length = (((off_t)part->end - begin) >= (off_t)sizeof(buffer))
431 ? sizeof(buffer) : (part->end - begin);
433 _io_data->ReadAt(begin,buffer,length);
434 amountWritten = render_to->Write(buffer,length);
435 if (amountWritten < 0)
436 return amountWritten; // IO error of some sort.
441 render_to->Write(delimiter.String(),delimiter.Length() - 2); // strip CRLF
442 render_to->Write("--\r\n",4);
444 return B_OK;
447 void BMIMEMultipartMailContainer::_ReservedMultipart1() {}
448 void BMIMEMultipartMailContainer::_ReservedMultipart2() {}
449 void BMIMEMultipartMailContainer::_ReservedMultipart3() {}
451 void BMailContainer::_ReservedContainer1() {}
452 void BMailContainer::_ReservedContainer2() {}
453 void BMailContainer::_ReservedContainer3() {}
454 void BMailContainer::_ReservedContainer4() {}