1/* Container - message part container class
2**
3** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
4*/
5
6
7#include <String.h>
8#include <List.h>
9#include <Mime.h>
10
11#include <stdlib.h>
12#include <strings.h>
13#include <unistd.h>
14
15class _EXPORT BMIMEMultipartMailContainer;
16
17#include <MailContainer.h>
18#include <MailAttachment.h>
19
20typedef struct message_part {
21	message_part(off_t start, off_t end) { this->start = start; this->end = end; }
22
23	// Offset where the part starts (includes MIME sub-headers but not the
24	// boundary line) in the message file.
25	int32 start;
26
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"
35    //
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;
42
43
44BMIMEMultipartMailContainer::BMIMEMultipartMailContainer(
45	const char *boundary,
46	const char *this_is_an_MIME_message_text,
47	uint32 defaultCharSet)
48	:
49	BMailContainer (defaultCharSet),
50	_boundary(NULL),
51	_MIME_message_warning(this_is_an_MIME_message_text),
52	_io_data(NULL)
53{
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);
58}
59
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	}*/
69
70
71BMIMEMultipartMailContainer::~BMIMEMultipartMailContainer() {
72	for (int32 i = 0; i < _components_in_raw.CountItems(); i++)
73		delete (message_part *)_components_in_raw.ItemAt(i);
74
75	for (int32 i = 0; i < _components_in_code.CountItems(); i++)
76		delete (BMailComponent *)_components_in_code.ItemAt(i);
77
78	free((void *)_boundary);
79}
80
81
82void BMIMEMultipartMailContainer::SetBoundary(const char *boundary) {
83	free ((void *) _boundary);
84	_boundary = NULL;
85	if (boundary != NULL)
86		_boundary = strdup(boundary);
87
88	BMessage structured;
89	HeaderField("Content-Type",&structured);
90
91	if (_boundary == NULL)
92		structured.RemoveName("boundary");
93	else if (structured.ReplaceString("boundary",_boundary) != B_OK)
94		structured.AddString("boundary",_boundary);
95
96	SetHeaderField("Content-Type",&structured);
97}
98
99
100void BMIMEMultipartMailContainer::SetThisIsAnMIMEMessageText(const char *text) {
101	_MIME_message_warning = text;
102}
103
104
105status_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;
110
111	_components_in_code.RemoveItem(component);
112	return B_ERROR;
113}
114
115
116BMailComponent *BMIMEMultipartMailContainer::GetComponent(int32 index, bool parse_now) {
117	if (index >= CountComponents())
118		return NULL;
119
120	if (BMailComponent *component = (BMailComponent *)_components_in_code.ItemAt(index))
121		return component;	//--- Handle easy case
122
123	message_part *part = (message_part *)(_components_in_raw.ItemAt(index));
124	if (part == NULL)
125		return NULL;
126
127	_io_data->Seek(part->start,SEEK_SET);
128
129	BMailComponent component (_charSetForTextDecoding);
130	if (component.SetToRFC822(_io_data,part->end - part->start) < B_OK)
131		return NULL;
132
133	BMailComponent *piece = component.WhatIsThis();
134
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);
142	*/
143	_io_data->Seek(part->start,SEEK_SET);
144	if (piece->SetToRFC822(_io_data,part->end - part->start, parse_now) < B_OK)
145	{
146		delete piece;
147		return NULL;
148	}
149	_components_in_code.ReplaceItem(index,piece);
150
151	return piece;
152}
153
154
155int32
156BMIMEMultipartMailContainer::CountComponents() const
157{
158	return _components_in_code.CountItems();
159}
160
161
162status_t
163BMIMEMultipartMailContainer::RemoveComponent(BMailComponent *component)
164{
165	if (component == NULL)
166		return B_BAD_VALUE;
167
168	int32 index = _components_in_code.IndexOf(component);
169	if (component == NULL)
170		return B_ENTRY_NOT_FOUND;
171
172	delete (BMailComponent *)_components_in_code.RemoveItem(index);
173	delete (message_part *)_components_in_raw.RemoveItem(index);
174
175	return B_OK;
176}
177
178
179status_t
180BMIMEMultipartMailContainer::RemoveComponent(int32 index)
181{
182	if (index >= CountComponents())
183		return B_BAD_INDEX;
184
185	delete (BMailComponent *)_components_in_code.RemoveItem(index);
186	delete (message_part *)_components_in_raw.RemoveItem(index);
187
188	return B_OK;
189}
190
191
192status_t BMIMEMultipartMailContainer::GetDecodedData(BPositionIO *)
193{
194	return B_BAD_TYPE; //------We don't play dat
195}
196
197
198status_t BMIMEMultipartMailContainer::SetDecodedData(BPositionIO *) {
199	return B_BAD_TYPE; //------We don't play dat
200}
201
202
203status_t BMIMEMultipartMailContainer::SetToRFC822(BPositionIO *data, size_t length, bool copy_data)
204{
205	typedef enum LookingForEnum {
206		FIRST_NEWLINE,
207		INITIAL_DASHES,
208		BOUNDARY_BODY,
209		LAST_NEWLINE,
210		MAX_LOOKING_STATES
211	} LookingFor;
212
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;
230
231	// Clear out old components.  Maybe make a MakeEmpty method?
232
233	for (i = _components_in_code.CountItems(); i-- > 0;)
234		delete (BMailComponent *)_components_in_code.RemoveItem(i);
235
236	for (i = _components_in_raw.CountItems(); i-- > 0;)
237		delete (message_part *)_components_in_raw.RemoveItem(i);
238
239	// Start by reading the headers and getting the boundary string.
240
241	_io_data = data;
242	topLevelStart = data->Position();
243	topLevelEnd = topLevelStart + length;
244
245	BMailComponent::SetToRFC822(data,length);
246
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;
252
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.
260
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.
266
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)
276	{
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)
280		{
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;
287
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;
299			}
300			buffer [bufferSize] = 0; // Add an end of string NUL byte.
301		}
302
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.
311
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;
326
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;
334
335			case BOUNDARY_BODY:
336				if (strncmp (buffer + bufferIndex, _boundary, boundaryLength) != 0) {
337					state = FIRST_NEWLINE;
338					break;
339				}
340				bufferIndex += boundaryLength;
341				finalBoundary = false;
342				if (buffer[bufferIndex] == '-' && buffer[bufferIndex + 1] == '-') {
343					bufferIndex += 2;
344					finalBoundary = true;
345				}
346				state = LAST_NEWLINE;
347				break;
348
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;
360				}
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);
366				}
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;
373
374			default: // Should not happen.
375				state = FIRST_NEWLINE;
376		}
377	}
378
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.
383
384	if (!finalComponentCompleted
385		&& lastBoundaryOffset >= 0 && lastBoundaryOffset < topLevelEnd) {
386		_components_in_raw.AddItem (new message_part (lastBoundaryOffset, topLevelEnd));
387		_components_in_code.AddItem (NULL);
388	}
389
390	// If requested, actually read the data inside each component, otherwise
391	// only the positions in the BPositionIO are recorded.
392
393	if (copy_data) {
394		for (i = 0; GetComponent(i, true /* parse_now */) != NULL; i++) {}
395	}
396
397	data->Seek (topLevelEnd, SEEK_SET);
398	return B_OK;
399}
400
401
402status_t BMIMEMultipartMailContainer::RenderToRFC822(BPositionIO *render_to) {
403	BMailComponent::RenderToRFC822(render_to);
404
405	BString delimiter;
406	delimiter << "\r\n--" << _boundary << "\r\n";
407
408	if (_MIME_message_warning != NULL) {
409		render_to->Write(_MIME_message_warning,strlen(_MIME_message_warning));
410		render_to->Write("\r\n",2);
411	}
412
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
416
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
423
424			uint8 buffer[1024];
425			ssize_t amountWritten, length;
426			message_part *part = (message_part *)_components_in_raw.ItemAt(i);
427
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);
432
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.
437			}
438		}
439	}
440
441	render_to->Write(delimiter.String(),delimiter.Length() - 2);	// strip CRLF
442	render_to->Write("--\r\n",4);
443
444	return B_OK;
445}
446
447void BMIMEMultipartMailContainer::_ReservedMultipart1() {}
448void BMIMEMultipartMailContainer::_ReservedMultipart2() {}
449void BMIMEMultipartMailContainer::_ReservedMultipart3() {}
450
451void BMailContainer::_ReservedContainer1() {}
452void BMailContainer::_ReservedContainer2() {}
453void BMailContainer::_ReservedContainer3() {}
454void BMailContainer::_ReservedContainer4() {}
455
456