xref: /haiku/src/kits/mail/MailContainer.cpp (revision 3aeed6607cd07762c0e709633c012b3a632dbad9)
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 
15 class _EXPORT BMIMEMultipartMailContainer;
16 
17 #include <MailContainer.h>
18 #include <MailAttachment.h>
19 
20 typedef struct message_part {
message_partmessage_part21 	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 
BMIMEMultipartMailContainer(const char * boundary,const char * this_is_an_MIME_message_text,uint32 defaultCharSet)44 BMIMEMultipartMailContainer::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 
~BMIMEMultipartMailContainer()71 BMIMEMultipartMailContainer::~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 
SetBoundary(const char * boundary)82 void 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 
SetThisIsAnMIMEMessageText(const char * text)100 void BMIMEMultipartMailContainer::SetThisIsAnMIMEMessageText(const char *text) {
101 	_MIME_message_warning = text;
102 }
103 
104 
AddComponent(BMailComponent * component)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;
110 
111 	_components_in_code.RemoveItem(component);
112 	return B_ERROR;
113 }
114 
115 
GetComponent(int32 index,bool parse_now)116 BMailComponent *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 
155 int32
CountComponents() const156 BMIMEMultipartMailContainer::CountComponents() const
157 {
158 	return _components_in_code.CountItems();
159 }
160 
161 
162 status_t
RemoveComponent(BMailComponent * component)163 BMIMEMultipartMailContainer::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 
179 status_t
RemoveComponent(int32 index)180 BMIMEMultipartMailContainer::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 
GetDecodedData(BPositionIO *)192 status_t BMIMEMultipartMailContainer::GetDecodedData(BPositionIO *)
193 {
194 	return B_BAD_TYPE; //------We don't play dat
195 }
196 
197 
SetDecodedData(BPositionIO *)198 status_t BMIMEMultipartMailContainer::SetDecodedData(BPositionIO *) {
199 	return B_BAD_TYPE; //------We don't play dat
200 }
201 
202 
SetToRFC822(BPositionIO * data,size_t length,bool copy_data)203 status_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 
RenderToRFC822(BPositionIO * render_to)402 status_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 
_ReservedMultipart1()447 void BMIMEMultipartMailContainer::_ReservedMultipart1() {}
_ReservedMultipart2()448 void BMIMEMultipartMailContainer::_ReservedMultipart2() {}
_ReservedMultipart3()449 void BMIMEMultipartMailContainer::_ReservedMultipart3() {}
450 
_ReservedContainer1()451 void BMailContainer::_ReservedContainer1() {}
_ReservedContainer2()452 void BMailContainer::_ReservedContainer2() {}
_ReservedContainer3()453 void BMailContainer::_ReservedContainer3() {}
_ReservedContainer4()454 void BMailContainer::_ReservedContainer4() {}
455 
456