Пример #1
0
/* Writes the headers as text/plain.
   This writes out a blank line after the headers, unless
   dont_write_content_type is true, in which case the header-block
   is not closed off, and none of the Content- headers are written.
 */
int
MimeHeaders_write_raw_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt,
                 bool dont_write_content_type)
{
  int status;

  if (hdrs && !hdrs->done_p)
  {
    hdrs->done_p = true;
    status = MimeHeaders_build_heads_list(hdrs);
    if (status < 0) return 0;
  }

  nsCString name;
  name.Adopt(MimeHeaders_get_name(hdrs, opt));
  MimeHeaders_convert_header_value(opt, name, false);

  if (!dont_write_content_type)
  {
    char nl[] = MSG_LINEBREAK;
    if (hdrs)
    {
      status = MimeHeaders_write(opt, name, hdrs->all_headers,
                                 hdrs->all_headers_fp);
      if (status < 0) return status;
    }
    status = MimeHeaders_write(opt, name, nl, strlen(nl));
    if (status < 0) return status;
  }
  else if (hdrs)
  {
    int32_t i;
    for (i = 0; i < hdrs->heads_size; i++)
    {
      char *head = hdrs->heads[i];
      char *end = (i == hdrs->heads_size-1
             ? hdrs->all_headers + hdrs->all_headers_fp
             : hdrs->heads[i+1]);

      NS_ASSERTION(head, "1.22 <*****@*****.**> 22 Aug 1999 08:48");
      if (!head) continue;

      /* Don't write out any Content- header. */
      if (!PL_strncasecmp(head, "Content-", 8))
      continue;

      /* Write out this (possibly multi-line) header. */
      status = MimeHeaders_write(opt, name, head, end - head);
      if (status < 0) return status;
    }
  }

  if (hdrs)
    MimeHeaders_compact(hdrs);

  return 0;
}
Пример #2
0
static int
MimeInlineTextPlain_parse_line (char *line, PRInt32 length, MimeObject *obj)
{
  int status;
  PRBool quoting = ( obj->options
    && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
         obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
       )           );  // see above
  PRBool plainHTML = quoting || (obj->options &&
       obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs);
       // see above

  PRBool rawPlainText = obj->options &&
       (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer
       || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach);

  // this routine gets called for every line of data that comes through the
  // mime converter. It's important to make sure we are efficient with 
  // how we allocate memory in this routine. be careful if you go to add
  // more to this routine.

  NS_ASSERTION(length > 0, "zero length");
  if (length <= 0) return 0;

  mozITXTToHTMLConv *conv = GetTextConverter(obj->options);
  MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;

  PRBool skipConversion = !conv || rawPlainText ||
                          (obj->options && obj->options->force_user_charset);

  char *mailCharset = NULL;
  nsresult rv;

  // if this part has a name and it's not a message/rfc822, don't quote
  if (quoting && obj->headers && MimeHeaders_get_name(obj->headers, obj->options) 
      && PL_strcasecmp(obj->content_type, MESSAGE_RFC822))
    return 0;

  if (!skipConversion)
  {
    nsDependentCString inputStr(line, length);
    nsAutoString lineSourceStr;

    // For 'SaveAs', |line| is in |mailCharset|.
    // convert |line| to UTF-16 before 'html'izing (calling ScanTXT())
    if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)
    { // Get the mail charset of this message.
      MimeInlineText  *inlinetext = (MimeInlineText *) obj;
      if (!inlinetext->initializeCharset)
         ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj);
      mailCharset = inlinetext->charset;
      if (mailCharset && *mailCharset) {
        rv = nsMsgI18NConvertToUnicode(mailCharset, inputStr, lineSourceStr);
        NS_ENSURE_SUCCESS(rv, -1);
      }
      else // this probably never happens ...
        CopyUTF8toUTF16(inputStr, lineSourceStr);
    }
    else  // line is in UTF-8
      CopyUTF8toUTF16(inputStr, lineSourceStr);

    nsCAutoString prefaceResultStr;  // Quoting stuff before the real text

    // Recognize quotes
    PRUint32 oldCiteLevel = text->mCiteLevel;
    PRUint32 logicalLineStart = 0;
    rv = conv->CiteLevelTXT(lineSourceStr.get(),
                            &logicalLineStart, &(text->mCiteLevel));
    NS_ENSURE_SUCCESS(rv, -1);

    // Find out, which recognitions to do
    PRBool whattodo = obj->options->whattodo;
    if (plainHTML)
    {
      if (quoting)
        whattodo = 0;  // This is done on Send. Don't do it twice.
      else
        whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution;
                   /* Do recognition for the case, the result is viewed in
                      Mozilla, but not GlyphSubstitution, because other UAs
                      might not be able to display the glyphs. */
      if (!text->mBlockquoting)
        text->mCiteLevel = 0;
    }

    // Write blockquote
    if (text->mCiteLevel > oldCiteLevel)
    {
      prefaceResultStr += "</pre>";
      for (PRUint32 i = 0; i < text->mCiteLevel - oldCiteLevel; i++)
      {
        nsCAutoString style;
        MimeTextBuildPrefixCSS(text->mQuotedSizeSetting, text->mQuotedStyleSetting,
                               text->mCitationColor, style);
        if (!plainHTML && !style.IsEmpty())
        {
          prefaceResultStr += "<blockquote type=cite style=\"";
          prefaceResultStr += style;
          prefaceResultStr += "\">";
        }
        else
          prefaceResultStr += "<blockquote type=cite>";
      }
      prefaceResultStr += "<pre wrap>";
    }
    else if (text->mCiteLevel < oldCiteLevel)
    {
      prefaceResultStr += "</pre>";
      for (PRUint32 i = 0; i < oldCiteLevel - text->mCiteLevel; i++)
        prefaceResultStr += "</blockquote>";
      prefaceResultStr += "<pre wrap>";
      if (text->mCiteLevel == 0)
        prefaceResultStr += "<!---->";   /* Make sure, NGLayout puts out
                                            a linebreak */
    }

    // Write plain text quoting tags
    if (logicalLineStart != 0 && !(plainHTML && text->mBlockquoting))
    {
      if (!plainHTML)
        prefaceResultStr += "<span class=\"moz-txt-citetags\">";

      nsAutoString citeTagsSource;
      lineSourceStr.Mid(citeTagsSource, 0, logicalLineStart);

      // Convert to HTML
      nsXPIDLString citeTagsResultUnichar;
      rv = conv->ScanTXT(citeTagsSource.get(), 0 /* no recognition */,
                         getter_Copies(citeTagsResultUnichar));
      if (NS_FAILED(rv)) return -1;

      AppendUTF16toUTF8(citeTagsResultUnichar, prefaceResultStr);
      if (!plainHTML)
        prefaceResultStr += "</span>";
    }


    // recognize signature
    if ((lineSourceStr.Length() >= 4)
        && lineSourceStr.First() == '-'
        && Substring(lineSourceStr, 0, 3).EqualsLiteral("-- ")
        && (lineSourceStr[3] == '\r' || lineSourceStr[3] == '\n') )
    {
      text->mIsSig = PR_TRUE;
      if (!quoting)
        prefaceResultStr += "<div class=\"moz-txt-sig\">";
    }


    /* This is the main TXT to HTML conversion:
       escaping (very important), eventually recognizing etc. */
    nsXPIDLString lineResultUnichar;

    rv = conv->ScanTXT(lineSourceStr.get() + logicalLineStart,
                       whattodo, getter_Copies(lineResultUnichar));
    NS_ENSURE_SUCCESS(rv, -1);

    if (!(text->mIsSig && quoting))
    {
      status = MimeObject_write(obj, prefaceResultStr.get(), prefaceResultStr.Length(), PR_TRUE);
      if (status < 0) return status;
      nsCAutoString outString;
      if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs ||
          !mailCharset || !*mailCharset)
        CopyUTF16toUTF8(lineResultUnichar, outString);
      else
      { // convert back to mailCharset before writing.       
        rv = nsMsgI18NConvertFromUnicode(mailCharset, 
                                         lineResultUnichar, outString);
        NS_ENSURE_SUCCESS(rv, -1);
      }

      status = MimeObject_write(obj, outString.get(), outString.Length(), PR_TRUE);
    }
    else
    {
      status = NS_OK;
    }
  }
  else
  {
    status = MimeObject_write(obj, line, length, PR_TRUE);
  }

  return status;
}
Пример #3
0
static int
MimeInlineTextPlain_parse_eof (MimeObject *obj, PRBool abort_p)
{
  int status;

  // Has this method already been called for this object?
  // In that case return.
  if (obj->closed_p) return 0;

  nsXPIDLCString citationColor;
  MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
  if (text && text->mCitationColor)
    citationColor.Adopt(text->mCitationColor);

  PRBool quoting = ( obj->options
    && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting ||
         obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting
       )           );  // see above
  
  PRBool rawPlainText = obj->options &&
       (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer
        || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach);

  /* Run parent method first, to flush out any buffered data. */
  status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
  if (status < 0) return status;

  // if this part has a name and it's not a message/rfc822, don't quote
  if (quoting && obj->headers && MimeHeaders_get_name(obj->headers, obj->options) 
      && PL_strcasecmp(obj->content_type, MESSAGE_RFC822))
    return 0;

  if (!obj->output_p) return 0;

  if (obj->options &&
	  obj->options->write_html_p &&
	  obj->options->output_fn &&
	  !abort_p && !rawPlainText)
	{
      MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj;
      if (text->mIsSig && !quoting)
      {
        status = MimeObject_write(obj, "</div>", 6, PR_FALSE);  // .moz-txt-sig
        if (status < 0) return status;
      }
      status = MimeObject_write(obj, "</pre>", 6, PR_FALSE);
      if (status < 0) return status;
      if (!quoting)
      {
        status = MimeObject_write(obj, "</div>", 6, PR_FALSE);
                                        // .moz-text-plain
        if (status < 0) return status;
      }

      /* text/plain objects always have separators before and after them.
		 Note that this is not the case for text/enriched objects.
	   */
	  status = MimeObject_write_separator(obj);
	  if (status < 0) return status;
	}

  return 0;
}
Пример #4
0
static int
MimeInlineImage_parse_begin (MimeObject *obj)
{
  MimeInlineImage *img = (MimeInlineImage *) obj;
  MimeInlineImageClass *clazz;

  int status;

  status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj);
  if (status < 0) return status;

  if (!obj->output_p) return 0;

  if (!obj->options || !obj->options->output_fn ||
      // don't bother processing if the consumer doesn't want us
      //  gunking the body up.
      obj->options->write_pure_bodies)
    return 0;

  clazz = (MimeInlineImageClass *) obj->clazz;

  if (obj->options &&
    obj->options->image_begin &&
    obj->options->write_html_p &&
    obj->options->image_write_buffer)
  {
    char *html, *part, *image_url;
    const char *ct;

    part = mime_part_address(obj);
    if (!part) return MIME_OUT_OF_MEMORY;

      char *no_part_url = nullptr;
      if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
        no_part_url = mime_get_base_url(obj->options->url);

        if (no_part_url)
        {
          image_url = mime_set_url_part(no_part_url, part, true);
          PR_Free(no_part_url);
        }
        else
          image_url = mime_set_url_part(obj->options->url, part, true);

    if (!image_url)
    {
      PR_Free(part);
      return MIME_OUT_OF_MEMORY;
    }
    PR_Free(part);

    ct = obj->content_type;
    if (!ct) ct = IMAGE_GIF;  /* Can't happen?  Close enough. */

    // Fill in content type and attachment name here.
    nsCAutoString url_with_filename(image_url);
    url_with_filename += "&type=";
    url_with_filename += ct;
    char * filename = MimeHeaders_get_name ( obj->headers, obj->options );
    if (filename)
    {
      nsCString escapedName;
      MsgEscapeString(nsDependentCString(filename), nsINetUtil::ESCAPE_URL_PATH,
                      escapedName);
      url_with_filename += "&filename=";
      url_with_filename += escapedName;
      PR_Free(filename);
    }

    // We need to separate images with HR's...
    MimeObject_write_separator(obj);

    img->image_data =
      obj->options->image_begin(url_with_filename.get(), ct, obj->options->stream_closure);
    PR_Free(image_url);

    if (!img->image_data) return MIME_OUT_OF_MEMORY;

    html = obj->options->make_image_html(img->image_data);
    if (!html) return MIME_OUT_OF_MEMORY;

    status = MimeObject_write(obj, html, strlen(html), true);
    PR_Free(html);
    if (status < 0) return status;
  }

  //
  // Now we are going to see if we should set the content type in the
  // URI for the url being run...
  //
  if (obj->options && obj->options->stream_closure && obj->content_type)
  {
    mime_stream_data  *msd = (mime_stream_data *) (obj->options->stream_closure);
    if ( (msd) && (msd->channel) )
    {
      msd->channel->SetContentType(nsDependentCString(obj->content_type));
    }
  }

  return 0;
}
Пример #5
0
static bool
MimeMultipartRelated_output_child_p(MimeObject *obj, MimeObject* child)
{
  MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;

  /* rhp - Changed from "if (relobj->head_loaded)" alone to support the
           start parameter
   */
  if (
       (relobj->head_loaded) ||
       (MimeStartParamExists(obj, child) && !MimeThisIsStartPart(obj, child))
     )
  {
    /* This is a child part.  Just remember the mapping between the URL
       it represents and the part-URL to get it back. */

    char* location = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION,
                     false, false);
    if (!location) {
      char* tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID,
                    false, false);
      if (tmp) {
        char* tmp2 = tmp;
        if (*tmp2 == '<') {
          int length;
          tmp2++;
          length = strlen(tmp2);
          if (length > 0 && tmp2[length - 1] == '>') {
            tmp2[length - 1] = '\0';
          }
        }
        location = PR_smprintf("cid:%s", tmp2);
        PR_Free(tmp);
      }
    }

    if (location) {
      char *absolute;
      char *base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
                       false, false);
      absolute = MakeAbsoluteURL(base_url ? base_url : relobj->base_url, location);

      PR_FREEIF(base_url);
      PR_Free(location);
      if (absolute) {
        nsCAutoString partnum;
        nsCAutoString imappartnum;
        partnum.Adopt(mime_part_address(child));
        if (!partnum.IsEmpty()) {
          if (obj->options->missing_parts)
          {
            char * imappart = mime_imap_part_address(child);
            if (imappart)
              imappartnum.Adopt(imappart);
          }

          /*
            AppleDouble part need special care: we need to output only the data fork part of it.
            The problem at this point is that we haven't yet decoded the children of the AppleDouble
            part therfore we will have to hope the datafork is the second one!
          */
          if (mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
            partnum.Append(".2");

          char* part;
          if (!imappartnum.IsEmpty())
            part = mime_set_url_imap_part(obj->options->url, imappartnum.get(), partnum.get());
          else
          {
            char *no_part_url = nullptr;
            if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
              no_part_url = mime_get_base_url(obj->options->url);
            if (no_part_url)
            {
              part = mime_set_url_part(no_part_url, partnum.get(), false);
              PR_Free(no_part_url);
            }
            else
              part = mime_set_url_part(obj->options->url, partnum.get(), false);
          }
          if (part)
          {
            char *name = MimeHeaders_get_name(child->headers, child->options);
            // let's stick the filename in the part so save as will work.
            if (name)
            {
              //char *savePart = part;
              //part = PR_smprintf("%s&filename=%s", savePart, name);
              //PR_Free(savePart);
              PR_Free(name);
            }
            char *temp = part;
            /* If there's a space in the url, escape the url.
               (This happens primarily on Windows and Unix.) */
            if (PL_strchr(part, ' ') || PL_strchr(part, '>') || PL_strchr(part, '%'))
              temp = escape_for_mrel_subst(part);
              MimeHashValue * value = new MimeHashValue(child, temp);
              PL_HashTableAdd(relobj->hash, absolute, value);

            /* rhp - If this part ALSO has a Content-ID we need to put that into
                     the hash table and this is what this code does
             */
            {
              char *tloc;
              char *tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false);
              if (tmp)
              {
                char* tmp2 = tmp;
                if (*tmp2 == '<')
                {
                  int length;
                  tmp2++;
                  length = strlen(tmp2);
                  if (length > 0 && tmp2[length - 1] == '>')
                  {
                    tmp2[length - 1] = '\0';
                  }
                }

                tloc = PR_smprintf("cid:%s", tmp2);
                PR_Free(tmp);
                if (tloc)
                {
                  MimeHashValue *value;
                  value = (MimeHashValue*)PL_HashTableLookup(relobj->hash, tloc);

                  if (!value)
                  {
                    value = new MimeHashValue(child, temp);
                    PL_HashTableAdd(relobj->hash, tloc, value);
                  }
                  else
                    PR_smprintf_free(tloc);
                }
              }
            }
            /*  rhp - End of putting more stuff into the hash table */

              /* it's possible that temp pointer is the same than the part pointer,
                 therefore be carefull to not freeing twice the same pointer */
              if (temp && temp != part)
                PR_Free(temp);
              PR_Free(part);
            }
          }
        }
      }
  } else {
    /* Ah-hah!  We're the head object.  */
    char* base_url;
    relobj->head_loaded = true;
    relobj->headobj = child;
    relobj->buffered_hdrs = MimeHeaders_copy(child->headers);
    base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
                   false, false);
    /* rhp: need this for supporting Content-Location */
    if (!base_url)
    {
      base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false, false);
    }
    /* rhp: need this for supporting Content-Location */

    if (base_url) {
      /* If the head object has a base_url associated with it, use
         that instead of any base_url that may have been associated
         with the multipart/related. */
      PR_FREEIF(relobj->base_url);
      relobj->base_url = base_url;
    }
  }
  if (obj->options && !obj->options->write_html_p
#ifdef MIME_DRAFTS
    && !obj->options->decompose_file_p
#endif /* MIME_DRAFTS */
    )
    {
    return true;
    }

  return false;      /* Don't actually parse this child; we'll handle
                 all that at eof time. */
}
Пример #6
0
static int
MimeMultipart_parse_line (const char *line, PRInt32 length, MimeObject *obj)
{
  MimeMultipart *mult = (MimeMultipart *) obj;
  int status = 0;
  MimeMultipartBoundaryType boundary;

  NS_ASSERTION(line && *line, "empty line in multipart parse_line");
  if (!line || !*line) return -1;

  NS_ASSERTION(!obj->closed_p, "obj shouldn't already be closed");
  if (obj->closed_p) return -1;

  /* If we're supposed to write this object, but aren't supposed to convert
     it to HTML, simply pass it through unaltered. */
  if (obj->output_p &&
    obj->options &&
    !obj->options->write_html_p &&
    obj->options->output_fn
          && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
  return MimeObject_write(obj, line, length, PR_TRUE);


  if (mult->state == MimeMultipartEpilogue)  /* already done */
    boundary = MimeMultipartBoundaryTypeNone;
  else
    boundary = ((MimeMultipartClass *)obj->clazz)->check_boundary(obj, line,
                                                                  length);

  if (boundary == MimeMultipartBoundaryTypeTerminator ||
    boundary == MimeMultipartBoundaryTypeSeparator)
  {
  /* Match!  Close the currently-open part, move on to the next
     state, and discard this line.
   */
    PRBool endOfPart = (mult->state != MimeMultipartPreamble);
    if (endOfPart)
      status = ((MimeMultipartClass *)obj->clazz)->close_child(obj);
    if (status < 0) return status;
    
    if (boundary == MimeMultipartBoundaryTypeTerminator)
      mult->state = MimeMultipartEpilogue;
    else
    {
      mult->state = MimeMultipartHeaders;
      
      /* Reset the header parser for this upcoming part. */
      NS_ASSERTION(!mult->hdrs, "mult->hdrs should be null here");
      if (mult->hdrs)
        MimeHeaders_free(mult->hdrs);
      mult->hdrs = MimeHeaders_new();
      if (!mult->hdrs)
        return MIME_OUT_OF_MEMORY;
      if (obj->options && obj->options->state &&
          obj->options->state->partsToStrip.Length() > 0)
      {
        nsCAutoString newPart(mime_part_address(obj));
        MimeContainer *container = (MimeContainer*) obj; 
        newPart.Append('.');
        newPart.AppendInt(container->nchildren + 1);
        obj->options->state->strippingPart = PR_FALSE;
        // check if this is a sub-part of a part we're stripping.
        for (PRUint32 partIndex = 0; partIndex < obj->options->state->partsToStrip.Length(); partIndex++)
        {
          nsCString &curPartToStrip = obj->options->state->partsToStrip[partIndex];
          if (newPart.Find(curPartToStrip) == 0 && (newPart.Length() == curPartToStrip.Length() || newPart.CharAt(curPartToStrip.Length()) == '.'))
          {
            obj->options->state->strippingPart = PR_TRUE;
            if (partIndex < obj->options->state->detachToFiles.Length())
              obj->options->state->detachedFilePath = obj->options->state->detachToFiles[partIndex];
            break;
          }
        }
      }
    }
    
    // if stripping out attachments, write the boundary line. Otherwise, return
    // to ignore it.
    if (obj->options && obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)
    {
      // Because MimeMultipart_parse_child_line strips out the 
      // the CRLF of the last line before the end of a part, we need to add that
      // back in here.
      if (endOfPart)
        MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));

      status = MimeObject_write(obj, line, length, PR_TRUE);
    }
    return 0;
  }

  /* Otherwise, this isn't a boundary string.  So do whatever it is we
   should do with this line (parse it as a header, feed it to the
   child part, ignore it, etc.) */

  switch (mult->state)
  {
    case MimeMultipartPreamble:
    case MimeMultipartEpilogue:
      /* Ignore this line. */
      break;

    case MimeMultipartHeaders:
    /* Parse this line as a header for the sub-part. */
    {
      status = MimeHeaders_parse_line(line, length, mult->hdrs);
      if (status < 0) return status;
      
      // If this line is blank, we're now done parsing headers, and should
      // now examine the content-type to create this "body" part.
      //
      if (*line == '\r' || *line == '\n')
      {
        if (obj->options && obj->options->state &&
            obj->options->state->strippingPart)
        {
          PRBool detachingPart = obj->options->state->detachedFilePath.Length() > 0;

          nsCAutoString fileName;
          fileName.Adopt(MimeHeaders_get_name(mult->hdrs, obj->options));
          if (detachingPart)
          {
            char *contentType = MimeHeaders_get(mult->hdrs, "Content-Type", PR_FALSE, PR_FALSE);
            if (contentType)
            {
              MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Type: "));
              MimeWriteAString(obj, nsDependentCString(contentType));
              PR_Free(contentType);
            }
            MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));
            MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: attachment; filename=\""));
            MimeWriteAString(obj, fileName);
            MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK));
            MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-External-Attachment-URL: "));
            MimeWriteAString(obj, obj->options->state->detachedFilePath);
            MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK));
            MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-Altered: AttachmentDetached; date=\""));
          }
          else
          {
            nsCAutoString header("Content-Type: text/x-moz-deleted; name=\"Deleted: ");
            header.Append(fileName);
            status = MimeWriteAString(obj, header);
            if (status < 0) 
              return status;
            status = MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK"Content-Transfer-Encoding: 8bit"MSG_LINEBREAK));
            MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted: "));
            MimeWriteAString(obj, fileName);
            MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK"X-Mozilla-Altered: AttachmentDeleted; date=\""));
          }
          nsCString result;
          char timeBuffer[128];
          PRExplodedTime now;
          PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
          PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer),
                                 "%a %b %d %H:%M:%S %Y",
                                 &now);
          MimeWriteAString(obj, nsDependentCString(timeBuffer));
          MimeWriteAString(obj, NS_LITERAL_CSTRING("\""MSG_LINEBREAK));
          MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK"You deleted an attachment from this message. The original MIME headers for the attachment were:"MSG_LINEBREAK));
          MimeHeaders_write_raw_headers(mult->hdrs, obj->options, PR_FALSE);
        }
        status = ((MimeMultipartClass *) obj->clazz)->create_child(obj);
        if (status < 0) return status;
        NS_ASSERTION(mult->state != MimeMultipartHeaders,
                     "mult->state shouldn't be MimeMultipartHeaders");

        // Ok, at this point, we need to examine the headers and see if there
        // is a special charset (i.e. non US-ASCII) for this message. If so, 
        // we need to tell the emitter that this is the case for use in in any
        // possible reply or forward operation.
        //
        PRBool isBody = PR_FALSE;
        PRBool isAlternative = PR_FALSE;

        MimeContainer *container = (MimeContainer*) obj; 
        // check if we're stripping the part of this newly created child.
        if (container->children && container->nchildren > 0)
        {
          MimeObject *kid = container->children[container->nchildren-1];
          if (kid->output_p)
            kid->output_p = !(obj->options && obj->options->state &&
                              obj->options->state->strippingPart);
        }
        if (container->children && container->nchildren == 1)
        {
          PRBool isAlternativeOrRelated = PR_FALSE;
          isBody = MimeObjectChildIsMessageBody(obj, &isAlternativeOrRelated);

          // MimeObjectChildIsMessageBody returns false for "multipart/related"
          // but we want to use the first part charset if that's a body.
          // I don't want to change the behavior of MimeObjectChildIsMessageBody
          // which is used by other places, so do the body check here.
          if (!isBody && 
              isAlternativeOrRelated &&
              mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMultipartRelatedClass))
          {
            MimeObject *firstChild = container->children[0];
            char *disposition = MimeHeaders_get (firstChild->headers,
                                                 HEADER_CONTENT_DISPOSITION, 
                                                 PR_TRUE,
                                                 PR_FALSE);
            if (!disposition)
            {
              if (!PL_strcasecmp (firstChild->content_type, TEXT_PLAIN) ||
                  !PL_strcasecmp (firstChild->content_type, TEXT_HTML) ||
                  !PL_strcasecmp (firstChild->content_type, TEXT_MDL) ||
                  !PL_strcasecmp (firstChild->content_type, MULTIPART_ALTERNATIVE) ||
                  !PL_strcasecmp (firstChild->content_type, MULTIPART_RELATED) ||
                  !PL_strcasecmp (firstChild->content_type, MESSAGE_NEWS) ||
                  !PL_strcasecmp (firstChild->content_type, MESSAGE_RFC822))
                isBody = PR_TRUE;
            }
          }
        }
        else 
          isAlternative = mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMultipartAlternativeClass);

        // If "multipart/alternative" or the first part is a message body
        // then we should check for a charset and notify the emitter  
        // if one exists.
        if (obj->options &&
            ((isAlternative && mult->state != MimeMultipartSkipPartLine) ||
             isBody || obj->options->notify_nested_bodies))
        {
          {
            char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
            if (ct)
            {
              if (obj->options->notify_nested_bodies)
                mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE,
                                          ct);
             char *cset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL);
             if (cset)
             {
                mimeEmitterUpdateCharacterSet(obj->options, cset);
                if (!(obj->options->override_charset))
                  // Also set this charset to msgWindow
                  SetMailCharacterSetToMsgWindow(obj, cset);
              }

              PR_FREEIF(ct);
              PR_FREEIF(cset);
            }
            // no content type means text/plain.
            else if (obj->options->notify_nested_bodies)
            {
              mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE,
                                        "text/plain");
            }
            if (obj->options->notify_nested_bodies && container->nchildren)
            {
              MimeObject *kid = container->children[container->nchildren-1];
              char *part_path = mime_part_address(kid);
              if (part_path)
              {
                mimeEmitterAddHeaderField(obj->options,
                                          "x-jsemitter-part-path",
                                          part_path);
                PR_Free(part_path);
              }
            }
          }
        }
      }
      break;
    }

    case MimeMultipartPartFirstLine:
      /* Hand this line off to the sub-part. */
      status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj,
                                                  line, length, PR_TRUE));
      if (status < 0) return status;
      mult->state = MimeMultipartPartLine;
      break;

    case MimeMultipartPartLine:
      /* Hand this line off to the sub-part. */
      status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj,
                  line, length, PR_FALSE));
      if (status < 0) return status;
      break;

    case MimeMultipartSkipPartLine:
      /* we are skipping that part, therefore just ignore the line */
      break;

    default:
      NS_ERROR("unexpected state in parse line");
      return -1;
  }

  if (obj->options &&
      obj->options->format_out == nsMimeOutput::nsMimeMessageAttach &&
      (!(obj->options->state && obj->options->state->strippingPart) &&
      mult->state != MimeMultipartPartLine))
      return MimeObject_write(obj, line, length, PR_FALSE);
  return 0;
}