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 { 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 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 ©) : 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 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 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 100 void BMIMEMultipartMailContainer::SetThisIsAnMIMEMessageText(const char *text) { 101 _MIME_message_warning = text; 102 } 103 104 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 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 156 BMIMEMultipartMailContainer::CountComponents() const 157 { 158 return _components_in_code.CountItems(); 159 } 160 161 162 status_t 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 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 192 status_t BMIMEMultipartMailContainer::GetDecodedData(BPositionIO *) 193 { 194 return B_BAD_TYPE; //------We don't play dat 195 } 196 197 198 status_t BMIMEMultipartMailContainer::SetDecodedData(BPositionIO *) { 199 return B_BAD_TYPE; //------We don't play dat 200 } 201 202 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 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 447 void BMIMEMultipartMailContainer::_ReservedMultipart1() {} 448 void BMIMEMultipartMailContainer::_ReservedMultipart2() {} 449 void BMIMEMultipartMailContainer::_ReservedMultipart3() {} 450 451 void BMailContainer::_ReservedContainer1() {} 452 void BMailContainer::_ReservedContainer2() {} 453 void BMailContainer::_ReservedContainer3() {} 454 void BMailContainer::_ReservedContainer4() {} 455 456