1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "msgCore.h"
7 #include "mimemult.h"
8 #include "mimemoz2.h"
9 #include "mimeeobj.h"
10 
11 #include "prlog.h"
12 #include "prmem.h"
13 #include "plstr.h"
14 #include "prio.h"
15 #include "nsMimeStringResources.h"
16 #include "nsMimeTypes.h"
17 #include <ctype.h>
18 
19 #ifdef XP_MACOSX
20 extern MimeObjectClass mimeMultipartAppleDoubleClass;
21 #endif
22 
23 #define MIME_SUPERCLASS mimeContainerClass
24 MimeDefClass(MimeMultipart, MimeMultipartClass, mimeMultipartClass,
25              &MIME_SUPERCLASS);
26 
27 static int MimeMultipart_initialize(MimeObject*);
28 static void MimeMultipart_finalize(MimeObject*);
29 static int MimeMultipart_parse_line(const char* line, int32_t length,
30                                     MimeObject*);
31 static int MimeMultipart_parse_eof(MimeObject* object, bool abort_p);
32 
33 static MimeMultipartBoundaryType MimeMultipart_check_boundary(MimeObject*,
34                                                               const char*,
35                                                               int32_t);
36 static int MimeMultipart_create_child(MimeObject*);
37 static bool MimeMultipart_output_child_p(MimeObject*, MimeObject*);
38 static int MimeMultipart_parse_child_line(MimeObject*, const char*, int32_t,
39                                           bool);
40 static int MimeMultipart_close_child(MimeObject*);
41 
42 extern "C" MimeObjectClass mimeMultipartAlternativeClass;
43 extern "C" MimeObjectClass mimeMultipartRelatedClass;
44 extern "C" MimeObjectClass mimeMultipartSignedClass;
45 extern "C" MimeObjectClass mimeInlineTextVCardClass;
46 extern "C" MimeExternalObjectClass mimeExternalObjectClass;
47 extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
48 
49 #if defined(DEBUG) && defined(XP_UNIX)
50 static int MimeMultipart_debug_print(MimeObject*, PRFileDesc*, int32_t);
51 #endif
52 
MimeMultipartClassInitialize(MimeMultipartClass * clazz)53 static int MimeMultipartClassInitialize(MimeMultipartClass* clazz) {
54   MimeObjectClass* oclass = (MimeObjectClass*)clazz;
55   MimeMultipartClass* mclass = (MimeMultipartClass*)clazz;
56 
57   PR_ASSERT(!oclass->class_initialized);
58   oclass->initialize = MimeMultipart_initialize;
59   oclass->finalize = MimeMultipart_finalize;
60   oclass->parse_line = MimeMultipart_parse_line;
61   oclass->parse_eof = MimeMultipart_parse_eof;
62 
63   mclass->check_boundary = MimeMultipart_check_boundary;
64   mclass->create_child = MimeMultipart_create_child;
65   mclass->output_child_p = MimeMultipart_output_child_p;
66   mclass->parse_child_line = MimeMultipart_parse_child_line;
67   mclass->close_child = MimeMultipart_close_child;
68 
69 #if defined(DEBUG) && defined(XP_UNIX)
70   oclass->debug_print = MimeMultipart_debug_print;
71 #endif
72 
73   return 0;
74 }
75 
MimeMultipart_initialize(MimeObject * object)76 static int MimeMultipart_initialize(MimeObject* object) {
77   MimeMultipart* mult = (MimeMultipart*)object;
78   char* ct;
79 
80   /* This is an abstract class; it shouldn't be directly instantiated. */
81   PR_ASSERT(object->clazz != (MimeObjectClass*)&mimeMultipartClass);
82 
83   ct = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, false, false);
84   mult->boundary =
85       (ct ? MimeHeaders_get_parameter(ct, HEADER_PARM_BOUNDARY, NULL, NULL)
86           : 0);
87   PR_FREEIF(ct);
88   mult->state = MimeMultipartPreamble;
89   return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object);
90 }
91 
MimeMultipart_finalize(MimeObject * object)92 static void MimeMultipart_finalize(MimeObject* object) {
93   MimeMultipart* mult = (MimeMultipart*)object;
94 
95   object->clazz->parse_eof(object, false);
96 
97   PR_FREEIF(mult->boundary);
98   if (mult->hdrs) MimeHeaders_free(mult->hdrs);
99   mult->hdrs = 0;
100   ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object);
101 }
102 
MimeWriteAString(MimeObject * obj,const nsACString & string)103 int MimeWriteAString(MimeObject* obj, const nsACString& string) {
104   const nsCString& flatString = PromiseFlatCString(string);
105   return MimeObject_write(obj, flatString.get(), flatString.Length(), true);
106 }
107 
MimeMultipart_parse_line(const char * line,int32_t length,MimeObject * obj)108 static int MimeMultipart_parse_line(const char* line, int32_t length,
109                                     MimeObject* obj) {
110   MimeMultipart* mult = (MimeMultipart*)obj;
111   MimeContainer* container = (MimeContainer*)obj;
112   int status = 0;
113   MimeMultipartBoundaryType boundary;
114 
115   NS_ASSERTION(line && *line, "empty line in multipart parse_line");
116   if (!line || !*line) return -1;
117 
118   NS_ASSERTION(!obj->closed_p, "obj shouldn't already be closed");
119   if (obj->closed_p) return -1;
120 
121   /* If we're supposed to write this object, but aren't supposed to convert
122      it to HTML, simply pass it through unaltered. */
123   if (obj->output_p && obj->options && !obj->options->write_html_p &&
124       obj->options->output_fn &&
125       obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
126     return MimeObject_write(obj, line, length, true);
127 
128   if (mult->state == MimeMultipartEpilogue) /* already done */
129     boundary = MimeMultipartBoundaryTypeNone;
130   else
131     boundary =
132         ((MimeMultipartClass*)obj->clazz)->check_boundary(obj, line, length);
133 
134   if (boundary == MimeMultipartBoundaryTypeTerminator ||
135       boundary == MimeMultipartBoundaryTypeSeparator) {
136     /* Match!  Close the currently-open part, move on to the next
137        state, and discard this line.
138      */
139     bool endOfPart = (mult->state != MimeMultipartPreamble);
140     if (endOfPart) status = ((MimeMultipartClass*)obj->clazz)->close_child(obj);
141     if (status < 0) return status;
142 
143     if (boundary == MimeMultipartBoundaryTypeTerminator)
144       mult->state = MimeMultipartEpilogue;
145     else {
146       mult->state = MimeMultipartHeaders;
147 
148       /* Reset the header parser for this upcoming part. */
149       NS_ASSERTION(!mult->hdrs, "mult->hdrs should be null here");
150       if (mult->hdrs) MimeHeaders_free(mult->hdrs);
151       mult->hdrs = MimeHeaders_new();
152       if (!mult->hdrs) return MIME_OUT_OF_MEMORY;
153       if (obj->options && obj->options->state &&
154           obj->options->state->partsToStrip.Length() > 0) {
155         nsAutoCString newPart(mime_part_address(obj));
156         newPart.Append('.');
157         newPart.AppendInt(container->nchildren + 1);
158         obj->options->state->strippingPart = false;
159         // check if this is a sub-part of a part we're stripping.
160         for (uint32_t partIndex = 0;
161              partIndex < obj->options->state->partsToStrip.Length();
162              partIndex++) {
163           nsCString& curPartToStrip =
164               obj->options->state->partsToStrip[partIndex];
165           if (newPart.Find(curPartToStrip) == 0 &&
166               (newPart.Length() == curPartToStrip.Length() ||
167                newPart.CharAt(curPartToStrip.Length()) == '.')) {
168             obj->options->state->strippingPart = true;
169             if (partIndex < obj->options->state->detachToFiles.Length())
170               obj->options->state->detachedFilePath =
171                   obj->options->state->detachToFiles[partIndex];
172             break;
173           }
174         }
175       }
176     }
177 
178     // if stripping out attachments, write the boundary line. Otherwise, return
179     // to ignore it.
180     if (obj->options &&
181         obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) {
182       // Because MimeMultipart_parse_child_line strips out the
183       // the CRLF of the last line before the end of a part, we need to add that
184       // back in here.
185       if (endOfPart) MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK));
186 
187       status = MimeObject_write(obj, line, length, true);
188     }
189     return 0;
190   }
191 
192   /* Otherwise, this isn't a boundary string.  So do whatever it is we
193    should do with this line (parse it as a header, feed it to the
194    child part, ignore it, etc.) */
195 
196   switch (mult->state) {
197     case MimeMultipartPreamble:
198     case MimeMultipartEpilogue:
199       /* Ignore this line. */
200       break;
201 
202     case MimeMultipartHeaders:
203       /* Parse this line as a header for the sub-part. */
204       {
205         status = MimeHeaders_parse_line(line, length, mult->hdrs);
206         bool stripping = false;
207 
208         if (status < 0) return status;
209 
210         // If this line is blank, we're now done parsing headers, and should
211         // now examine the content-type to create this "body" part.
212         //
213         if (*line == '\r' || *line == '\n') {
214           if (obj->options && obj->options->state &&
215               obj->options->state->strippingPart) {
216             stripping = true;
217             bool detachingPart =
218                 obj->options->state->detachedFilePath.Length() > 0;
219 
220             nsAutoCString fileName;
221             fileName.Adopt(MimeHeaders_get_name(mult->hdrs, obj->options));
222             // clang-format off
223             if (detachingPart) {
224               char *contentType =
225                   MimeHeaders_get(mult->hdrs, "Content-Type", false, false);
226               if (contentType) {
227                 MimeWriteAString(obj, "Content-Type: "_ns);
228                 MimeWriteAString(obj, nsDependentCString(contentType));
229                 PR_Free(contentType);
230               }
231               MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK));
232               MimeWriteAString(obj, "Content-Disposition: attachment; filename=\""_ns);
233               MimeWriteAString(obj, fileName);
234               MimeWriteAString(obj, "\""_ns MSG_LINEBREAK);
235               MimeWriteAString(obj, "X-Mozilla-External-Attachment-URL: "_ns);
236               MimeWriteAString(obj, obj->options->state->detachedFilePath);
237               MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK));
238               MimeWriteAString(obj, "X-Mozilla-Altered: AttachmentDetached; date=\""_ns);
239             } else {
240               nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: ");
241               header.Append(fileName);
242               status = MimeWriteAString(obj, header);
243               if (status < 0) return status;
244               status = MimeWriteAString(obj, "\""_ns MSG_LINEBREAK
245                                                  "Content-Transfer-Encoding: 8bit"_ns MSG_LINEBREAK);
246               MimeWriteAString(obj, "Content-Disposition: inline; filename=\"Deleted: "_ns);
247               MimeWriteAString(obj, fileName);
248               MimeWriteAString(obj, "\""_ns MSG_LINEBREAK
249                                         "X-Mozilla-Altered: AttachmentDeleted; date=\""_ns);
250             }
251             nsCString result;
252             char timeBuffer[128];
253             PRExplodedTime now;
254             PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
255             PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer),
256                                    "%a %b %d %H:%M:%S %Y", &now);
257             MimeWriteAString(obj, nsDependentCString(timeBuffer));
258             MimeWriteAString(obj, "\""_ns MSG_LINEBREAK);
259             MimeWriteAString(obj, MSG_LINEBREAK
260                                       "You deleted an attachment from this message. The original "_ns
261                                       "MIME headers for the attachment were:"_ns MSG_LINEBREAK);
262             MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false);
263             // clang-format on
264           }
265           int32_t old_nchildren = container->nchildren;
266           status = ((MimeMultipartClass*)obj->clazz)->create_child(obj);
267           if (status < 0) return status;
268           NS_ASSERTION(mult->state != MimeMultipartHeaders,
269                        "mult->state shouldn't be MimeMultipartHeaders");
270 
271           if (!stripping && container->nchildren > old_nchildren &&
272               obj->options &&
273               !mime_typep(obj,
274                           (MimeObjectClass*)&mimeMultipartAlternativeClass)) {
275             // Notify emitter about content type and part path.
276             MimeObject* kid = container->children[container->nchildren - 1];
277             MimeMultipart_notify_emitter(kid);
278           }
279         }
280         break;
281       }
282 
283     case MimeMultipartPartFirstLine:
284       /* Hand this line off to the sub-part. */
285       status = (((MimeMultipartClass*)obj->clazz)
286                     ->parse_child_line(obj, line, length, true));
287       if (status < 0) return status;
288       mult->state = MimeMultipartPartLine;
289       break;
290 
291     case MimeMultipartPartLine:
292       /* Hand this line off to the sub-part. */
293       status = (((MimeMultipartClass*)obj->clazz)
294                     ->parse_child_line(obj, line, length, false));
295       if (status < 0) return status;
296       break;
297 
298     default:
299       NS_ERROR("unexpected state in parse line");
300       return -1;
301   }
302 
303   if (obj->options &&
304       obj->options->format_out == nsMimeOutput::nsMimeMessageAttach &&
305       (!(obj->options->state && obj->options->state->strippingPart) &&
306        mult->state != MimeMultipartPartLine))
307     return MimeObject_write(obj, line, length, false);
308   return 0;
309 }
310 
MimeMultipart_notify_emitter(MimeObject * obj)311 void MimeMultipart_notify_emitter(MimeObject* obj) {
312   char* ct = nullptr;
313 
314   NS_ASSERTION(obj->options,
315                "MimeMultipart_notify_emitter called with null options");
316   if (!obj->options) return;
317 
318   ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false);
319   if (obj->options->notify_nested_bodies) {
320     mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE,
321                               ct ? ct : TEXT_PLAIN);
322     char* part_path = mime_part_address(obj);
323     if (part_path) {
324       mimeEmitterAddHeaderField(obj->options, "x-jsemitter-part-path",
325                                 part_path);
326       PR_Free(part_path);
327     }
328   }
329 
330   // Examine the headers and see if there is a special charset
331   // (i.e. non US-ASCII) for this message. If so, we need to
332   // tell the emitter that this is the case for use in any
333   // possible reply or forward operation.
334   if (ct &&
335       (obj->options->notify_nested_bodies || MimeObjectIsMessageBody(obj))) {
336     char* cset = MimeHeaders_get_parameter(ct, "charset", NULL, NULL);
337     if (cset) {
338       mimeEmitterUpdateCharacterSet(obj->options, cset);
339       if (!obj->options->override_charset)
340         // Also set this charset to msgWindow
341         SetMailCharacterSetToMsgWindow(obj, cset);
342       PR_Free(cset);
343     }
344   }
345 
346   PR_FREEIF(ct);
347 }
348 
MimeMultipart_check_boundary(MimeObject * obj,const char * line,int32_t length)349 static MimeMultipartBoundaryType MimeMultipart_check_boundary(MimeObject* obj,
350                                                               const char* line,
351                                                               int32_t length) {
352   MimeMultipart* mult = (MimeMultipart*)obj;
353   int32_t blen;
354   bool term_p;
355 
356   if (!mult->boundary || line[0] != '-' || line[1] != '-')
357     return MimeMultipartBoundaryTypeNone;
358 
359   /* This is a candidate line to be a boundary.  Check it out... */
360   blen = strlen(mult->boundary);
361   term_p = false;
362 
363   /* strip trailing whitespace (including the newline.) */
364   while (length > 2 && IS_SPACE(line[length - 1])) length--;
365 
366   /* Could this be a terminating boundary? */
367   if (length == blen + 4 && line[length - 1] == '-' &&
368       line[length - 2] == '-') {
369     term_p = true;
370   }
371 
372   // looks like we have a separator but first, we need to check it's not for one
373   // of the part's children.
374   MimeContainer* cont = (MimeContainer*)obj;
375   if (cont->nchildren > 0) {
376     MimeObject* kid = cont->children[cont->nchildren - 1];
377     if (kid)
378       if (mime_typep(kid, (MimeObjectClass*)&mimeMultipartClass)) {
379         // Don't ask the kid to check the boundary if it has already detected a
380         // Teminator
381         MimeMultipart* mult = (MimeMultipart*)kid;
382         if (mult->state != MimeMultipartEpilogue)
383           if (MimeMultipart_check_boundary(kid, line, length) !=
384               MimeMultipartBoundaryTypeNone)
385             return MimeMultipartBoundaryTypeNone;
386       }
387   }
388 
389   if (term_p) length -= 2;
390 
391   if (blen == length - 2 && !strncmp(line + 2, mult->boundary, length - 2))
392     return (term_p ? MimeMultipartBoundaryTypeTerminator
393                    : MimeMultipartBoundaryTypeSeparator);
394   else
395     return MimeMultipartBoundaryTypeNone;
396 }
397 
MimeMultipart_create_child(MimeObject * obj)398 static int MimeMultipart_create_child(MimeObject* obj) {
399   MimeMultipart* mult = (MimeMultipart*)obj;
400   int status;
401   char* ct = (mult->hdrs ? MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE,
402                                            true, false)
403                          : 0);
404   const char* dct = (((MimeMultipartClass*)obj->clazz)->default_part_type);
405   MimeObject* body = NULL;
406 
407   mult->state = MimeMultipartPartFirstLine;
408   if (obj->options) obj->options->is_child = true;
409 
410   /* Don't pass in NULL as the content-type (this means that the
411    auto-uudecode-hack won't ever be done for subparts of a
412    multipart, but only for untyped children of message/rfc822.
413    */
414   body = mime_create(((ct && *ct) ? ct : (dct ? dct : TEXT_PLAIN)), mult->hdrs,
415                      obj->options);
416   PR_FREEIF(ct);
417   if (!body) return MIME_OUT_OF_MEMORY;
418   status = ((MimeContainerClass*)obj->clazz)->add_child(obj, body);
419   if (status < 0) {
420     mime_free(body);
421     return status;
422   }
423 
424 #ifdef MIME_DRAFTS
425   if (obj->options && obj->options->decompose_file_p &&
426       obj->options->is_multipart_msg && obj->options->decompose_file_init_fn) {
427     if (!mime_typep(obj, (MimeObjectClass*)&mimeMultipartRelatedClass) &&
428         !mime_typep(obj, (MimeObjectClass*)&mimeMultipartAlternativeClass) &&
429         !mime_typep(obj, (MimeObjectClass*)&mimeMultipartSignedClass) &&
430         /* bug 21869 -- due to the fact that we are not generating the
431            correct mime class object for content-typ multipart/signed part
432            the above check failed. to solve the problem in general and not
433            to cause early termination when parsing message for opening as
434            draft we can simply make sure that the child is not a multipart
435            mime object. this way we could have a proper decomposing message
436            part functions set correctly */
437         !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) &&
438         !((mime_typep(body, (MimeObjectClass*)&mimeExternalObjectClass) ||
439            mime_typep(body, (MimeObjectClass*)&mimeSuppressedCryptoClass)) &&
440           (!strcmp(body->content_type, "text/vcard") ||
441            !strcmp(body->content_type, "text/x-vcard")))) {
442       status = obj->options->decompose_file_init_fn(
443           obj->options->stream_closure, mult->hdrs);
444       if (status < 0) return status;
445     }
446   }
447 #endif /* MIME_DRAFTS */
448 
449   /* Now that we've added this new object to our list of children,
450    start its parser going (if we want to display it.)
451    */
452   body->output_p =
453       (((MimeMultipartClass*)obj->clazz)->output_child_p(obj, body));
454   if (body->output_p) {
455     status = body->clazz->parse_begin(body);
456 
457 #ifdef XP_MACOSX
458     /* if we are saving an apple double attachment, we need to set correctly the
459      * content type of the channel */
460     if (mime_typep(obj, (MimeObjectClass*)&mimeMultipartAppleDoubleClass)) {
461       mime_stream_data* msd = (mime_stream_data*)body->options->stream_closure;
462       if (!body->options->write_html_p && body->content_type &&
463           !PL_strcasecmp(body->content_type, APPLICATION_APPLEFILE)) {
464         if (msd && msd->channel)
465           msd->channel->SetContentType(nsLiteralCString(APPLICATION_APPLEFILE));
466       }
467     }
468 #endif
469 
470     if (status < 0) return status;
471   }
472 
473   return 0;
474 }
475 
MimeMultipart_output_child_p(MimeObject * obj,MimeObject * child)476 static bool MimeMultipart_output_child_p(MimeObject* obj, MimeObject* child) {
477   /* We don't output a child if we're stripping it. */
478   if (obj->options && obj->options->state && obj->options->state->strippingPart)
479     return false;
480   /* if we are saving an apple double attachment, ignore the appledouble wrapper
481    * part */
482   return (obj->options && obj->options->write_html_p) ||
483          PL_strcasecmp(child->content_type, MULTIPART_APPLEDOUBLE);
484 }
485 
MimeMultipart_close_child(MimeObject * object)486 static int MimeMultipart_close_child(MimeObject* object) {
487   MimeMultipart* mult = (MimeMultipart*)object;
488   MimeContainer* cont = (MimeContainer*)object;
489 
490   if (!mult->hdrs) return 0;
491 
492   MimeHeaders_free(mult->hdrs);
493   mult->hdrs = 0;
494 
495   NS_ASSERTION(cont->nchildren > 0, "badly formed mime message");
496   if (cont->nchildren > 0) {
497     MimeObject* kid = cont->children[cont->nchildren - 1];
498     // If we have a child and it has not already been closed, process it.
499     // The kid would be already be closed if we encounter a multipart section
500     // that did not have a fully delineated header block.  No header block means
501     // no creation of a new child, but the termination case still happens and
502     // we still end up here.  Obviously, we don't want to close the child a
503     // second time and the best thing we can do is nothing.
504     if (kid && !kid->closed_p) {
505       int status;
506       status = kid->clazz->parse_eof(kid, false);
507       if (status < 0) return status;
508       status = kid->clazz->parse_end(kid, false);
509       if (status < 0) return status;
510 
511 #ifdef MIME_DRAFTS
512       if (object->options && object->options->decompose_file_p &&
513           object->options->is_multipart_msg &&
514           object->options->decompose_file_close_fn) {
515         // clang-format off
516         if (!mime_typep(object, (MimeObjectClass *)&mimeMultipartRelatedClass) &&
517             !mime_typep(object, (MimeObjectClass *)&mimeMultipartAlternativeClass) &&
518             !mime_typep(object, (MimeObjectClass *)&mimeMultipartSignedClass) &&
519             /* bug 21869 -- due to the fact that we are not generating the
520                correct mime class object for content-typ multipart/signed part
521                the above check failed. to solve the problem in general and not
522                to cause early termination when parsing message for opening as
523                draft we can simply make sure that the child is not a multipart
524                mime object. this way we could have a proper decomposing message
525                part functions set correctly */
526             !mime_typep(kid, (MimeObjectClass *)&mimeMultipartClass) &&
527             !((mime_typep(kid, (MimeObjectClass *)&mimeExternalObjectClass) ||
528                mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
529               (!strcmp(kid->content_type, "text/vcard") ||
530                !strcmp(kid->content_type, "text/x-vcard")))) {
531           status = object->options->decompose_file_close_fn(
532               object->options->stream_closure);
533           if (status < 0) return status;
534         }
535         // clang-format on
536       }
537 #endif /* MIME_DRAFTS */
538     }
539   }
540   return 0;
541 }
542 
MimeMultipart_parse_child_line(MimeObject * obj,const char * line,int32_t length,bool first_line_p)543 static int MimeMultipart_parse_child_line(MimeObject* obj, const char* line,
544                                           int32_t length, bool first_line_p) {
545   MimeContainer* cont = (MimeContainer*)obj;
546   int status;
547   MimeObject* kid;
548 
549   PR_ASSERT(cont->nchildren > 0);
550   if (cont->nchildren <= 0) return -1;
551 
552   kid = cont->children[cont->nchildren - 1];
553   PR_ASSERT(kid);
554   if (!kid) return -1;
555 
556 #ifdef MIME_DRAFTS
557   if (obj->options && obj->options->decompose_file_p &&
558       obj->options->is_multipart_msg &&
559       obj->options->decompose_file_output_fn) {
560     if (!mime_typep(obj, (MimeObjectClass*)&mimeMultipartAlternativeClass) &&
561         !mime_typep(obj, (MimeObjectClass*)&mimeMultipartRelatedClass) &&
562         !mime_typep(obj, (MimeObjectClass*)&mimeMultipartSignedClass) &&
563         /* bug 21869 -- due to the fact that we are not generating the
564            correct mime class object for content-typ multipart/signed part
565            the above check failed. to solve the problem in general and not
566            to cause early termination when parsing message for opening as
567            draft we can simply make sure that the child is not a multipart
568            mime object. this way we could have a proper decomposing message
569            part functions set correctly */
570         !mime_typep(kid, (MimeObjectClass*)&mimeMultipartClass) &&
571         !((mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) ||
572            mime_typep(kid, (MimeObjectClass*)&mimeSuppressedCryptoClass)) &&
573           (!strcmp(kid->content_type, "text/vcard") ||
574            !strcmp(kid->content_type, "text/x-vcard"))))
575       return obj->options->decompose_file_output_fn(
576           line, length, obj->options->stream_closure);
577   }
578 #endif /* MIME_DRAFTS */
579 
580   /* The newline issues here are tricky, since both the newlines before
581    and after the boundary string are to be considered part of the
582    boundary: this is so that a part can be specified such that it
583    does not end in a trailing newline.
584 
585    To implement this, we send a newline *before* each line instead
586    of after, except for the first line, which is not preceded by a
587    newline.
588    */
589 
590   /* Remove the trailing newline... */
591   if (length > 0 && line[length - 1] == '\n') length--;
592   if (length > 0 && line[length - 1] == '\r') length--;
593 
594   if (!first_line_p) {
595     /* Push out a preceding newline... */
596     char nl[] = MSG_LINEBREAK;
597     status = kid->clazz->parse_buffer(nl, MSG_LINEBREAK_LEN, kid);
598     if (status < 0) return status;
599   }
600 
601   /* Now push out the line sans trailing newline. */
602   return kid->clazz->parse_buffer(line, length, kid);
603 }
604 
MimeMultipart_parse_eof(MimeObject * obj,bool abort_p)605 static int MimeMultipart_parse_eof(MimeObject* obj, bool abort_p) {
606   MimeMultipart* mult = (MimeMultipart*)obj;
607   MimeContainer* cont = (MimeContainer*)obj;
608 
609   if (obj->closed_p) return 0;
610 
611   /* Push out the last trailing line if there's one in the buffer.  If
612    this happens, this object does not end in a trailing newline (and
613    the parse_line method will be called with a string with no trailing
614    newline, which isn't the usual case.)
615    */
616   if (!abort_p && obj->ibuffer_fp > 0) {
617     /* There is leftover data without a terminating newline. */
618     int status = obj->clazz->parse_line(obj->ibuffer, obj->ibuffer_fp, obj);
619     obj->ibuffer_fp = 0;
620     if (status < 0) {
621       obj->closed_p = true;
622       return status;
623     }
624   }
625 
626   /* Now call parse_eof for our active child, if there is one.
627    */
628   if (cont->nchildren > 0 && (mult->state == MimeMultipartPartLine ||
629                               mult->state == MimeMultipartPartFirstLine)) {
630     MimeObject* kid = cont->children[cont->nchildren - 1];
631     NS_ASSERTION(kid, "not expecting null kid");
632     if (kid) {
633       int status = kid->clazz->parse_eof(kid, abort_p);
634       if (status < 0) return status;
635     }
636   }
637 
638   return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
639 }
640 
641 #if defined(DEBUG) && defined(XP_UNIX)
MimeMultipart_debug_print(MimeObject * obj,PRFileDesc * stream,int32_t depth)642 static int MimeMultipart_debug_print(MimeObject* obj, PRFileDesc* stream,
643                                      int32_t depth) {
644   /*  MimeMultipart *mult = (MimeMultipart *) obj; */
645   MimeContainer* cont = (MimeContainer*)obj;
646   char* addr = mime_part_address(obj);
647   int i;
648   for (i = 0; i < depth; i++) PR_Write(stream, "  ", 2);
649   /**
650     fprintf(stream, "<%s %s (%d kid%s) boundary=%s 0x%08X>\n",
651         obj->clazz->class_name,
652         addr ? addr : "???",
653         cont->nchildren, (cont->nchildren == 1 ? "" : "s"),
654         (mult->boundary ? mult->boundary : "(none)"),
655         (uint32_t) mult);
656   **/
657   PR_FREEIF(addr);
658 
659   /*
660     if (cont->nchildren > 0)
661     fprintf(stream, "\n");
662    */
663 
664   for (i = 0; i < cont->nchildren; i++) {
665     MimeObject* kid = cont->children[i];
666     int status = kid->clazz->debug_print(kid, stream, depth + 1);
667     if (status < 0) return status;
668   }
669 
670   /*
671     if (cont->nchildren > 0)
672     fprintf(stream, "\n");
673    */
674 
675   return 0;
676 }
677 #endif
678