1 /* Container - message part container class
3 ** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
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.
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
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.
44 BMIMEMultipartMailContainer::BMIMEMultipartMailContainer(
46 const char *this_is_an_MIME_message_text
,
47 uint32 defaultCharSet
)
49 BMailContainer (defaultCharSet
),
51 _MIME_message_warning(this_is_an_MIME_message_text
),
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 ©) :
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);
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
);
86 _boundary
= strdup(boundary
);
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
))
108 if (_components_in_raw
.AddItem(NULL
))
111 _components_in_code
.RemoveItem(component
);
116 BMailComponent
*BMIMEMultipartMailContainer::GetComponent(int32 index
, bool parse_now
) {
117 if (index
>= CountComponents())
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
));
127 _io_data
->Seek(part
->start
,SEEK_SET
);
129 BMailComponent
component (_charSetForTextDecoding
);
130 if (component
.SetToRFC822(_io_data
,part
->end
- part
->start
) < B_OK
)
133 BMailComponent
*piece
= component
.WhatIsThis();
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
)
149 _components_in_code
.ReplaceItem(index
,piece
);
156 BMIMEMultipartMailContainer::CountComponents() const
158 return _components_in_code
.CountItems();
163 BMIMEMultipartMailContainer::RemoveComponent(BMailComponent
*component
)
165 if (component
== NULL
)
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
);
180 BMIMEMultipartMailContainer::RemoveComponent(int32 index
)
182 if (index
>= CountComponents())
185 delete (BMailComponent
*)_components_in_code
.RemoveItem(index
);
186 delete (message_part
*)_components_in_raw
.RemoveItem(index
);
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
{
214 ssize_t amountToRead
;
215 ssize_t boundaryLength
;
220 BMessage content_type
;
221 const char *content_type_string
;
222 bool finalBoundary
= false;
223 bool finalComponentCompleted
= false;
225 off_t lastBoundaryOffset
;
227 off_t startOfBoundaryOffset
;
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.
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)
253 if (!content_type
.HasString("boundary"))
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
;
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
);
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.
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') {
319 state
= INITIAL_DASHES
;
320 } else if (buffer
[bufferIndex
] == '\n') {
322 state
= INITIAL_DASHES
;
328 if (buffer
[bufferIndex
] == '-' && buffer
[bufferIndex
+ 1] == '-') {
330 state
= BOUNDARY_BODY
;
332 state
= FIRST_NEWLINE
;
336 if (strncmp (buffer
+ bufferIndex
, _boundary
, boundaryLength
) != 0) {
337 state
= FIRST_NEWLINE
;
340 bufferIndex
+= boundaryLength
;
341 finalBoundary
= false;
342 if (buffer
[bufferIndex
] == '-' && buffer
[bufferIndex
+ 1] == '-') {
344 finalBoundary
= true;
346 state
= 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')
353 else if (buffer
[bufferIndex
] == '\n')
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.
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
;
370 finalComponentCompleted
= true;
371 state
= FIRST_NEWLINE
;
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
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.
394 for (i
= 0; GetComponent(i
, true /* parse_now */) != NULL
; i
++) {}
397 data
->Seek (topLevelEnd
, SEEK_SET
);
402 status_t
BMIMEMultipartMailContainer::RenderToRFC822(BPositionIO
*render_to
) {
403 BMailComponent::RenderToRFC822(render_to
);
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
422 // copy message contents
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);
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() {}