/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* mimei.c --- MIME parser, version 2 (see mimei.h).
   Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96.
 */

#include "mimeobj.h"	/*  MimeObject (abstract)							*/
#include "mimecont.h"	/*   |--- MimeContainer (abstract)					*/
#include "mimemult.h"	/*   |     |--- MimeMultipart (abstract)			*/
#include "mimemmix.h"	/*   |     |     |--- MimeMultipartMixed			*/
#include "mimemdig.h"	/*   |     |     |--- MimeMultipartDigest			*/
#include "mimempar.h"	/*   |     |     |--- MimeMultipartParallel			*/
#include "mimemalt.h"	/*   |     |     |--- MimeMultipartAlternative		*/
#include "mimemrel.h"	/*   |     |     |--- MimeMultipartRelated			*/
#include "mimemapl.h"	/*   |     |     |--- MimeMultipartAppleDouble		*/
#include "mimesun.h"	/*   |     |     |--- MimeSunAttachment				*/
#include "mimemsig.h"	/*   |     |     |--- MimeMultipartSigned (abstract)*/
#include "mimemsg.h"	/*   |     |--- MimeMessage							*/
#include "mimeunty.h"	/*   |     |--- MimeUntypedText						*/
#include "mimeleaf.h"	/*   |--- MimeLeaf (abstract)						*/
#include "mimetext.h"	/*   |     |--- MimeInlineText (abstract)			*/
#include "mimetpla.h"	/*   |     |     |--- MimeInlineTextPlain			*/
#include "mimethtm.h"	/*   |     |     |--- MimeInlineTextHTML			*/
#include "mimetric.h"	/*   |     |     |--- MimeInlineTextRichtext		*/
#include "mimetenr.h"	/*   |     |     |     |--- MimeInlineTextEnriched	*/
#ifndef MOZILLA_30
#include "mimevcrd.h"   /*   |     |     |--------- MimeInlineTextVCard		*/
#include "prefapi.h"
#endif /* !MOZILLA_30 */
#include "mimeiimg.h"	/*   |     |--- MimeInlineImage						*/
#include "mimeeobj.h"	/*   |     |--- MimeExternalObject					*/
#include "mimeebod.h"	/*   |--- MimeExternalBody							*/

#ifndef NO_SECURITY
#include "mimesec.h"
#endif /* NO_SECURITY */

/* ==========================================================================
   Allocation and destruction
   ==========================================================================
 */

static int mime_classinit(MimeObjectClass *class);

MimeObject *
mime_new (MimeObjectClass *class, MimeHeaders *hdrs,
		  const char *override_content_type)
{
  int size = class->instance_size;
  MimeObject *object;
  int status;

  /* Some assertions to verify that this isn't random junk memory... */
  XP_ASSERT(class->class_name && XP_STRLEN(class->class_name) > 0);
  XP_ASSERT(size > 0 && size < 1000);

  if (!class->class_initialized)
	{
	  status = mime_classinit(class);
	  if (status < 0) return 0;
	}

  XP_ASSERT(class->initialize && class->finalize);

  if (hdrs)
	{
	  hdrs = MimeHeaders_copy (hdrs);
	  if (!hdrs) return 0;
	}

  class->showAttachmentIcon = FALSE; /* initialize ricardob's new member. */
  object = (MimeObject *) XP_ALLOC(size);
  if (!object) return 0;

  XP_MEMSET(object, 0, size);
  object->class = class;
  object->headers = hdrs;

  if (override_content_type && *override_content_type)
	object->content_type = XP_STRDUP(override_content_type);

  status = class->initialize(object);
  if (status < 0)
	{
	  class->finalize(object);
	  XP_FREE(object);
	  return 0;
	}

  return object;
}

void
mime_free (MimeObject *object)
{
# ifdef DEBUG__
  int i, size = object->class->instance_size;
  uint32 *array = (uint32*) object;
# endif /* DEBUG */

  object->class->finalize(object);

# ifdef DEBUG__
  for (i = 0; i < (size / sizeof(*array)); i++)
	array[i] = (uint32) 0xDEADBEEF;
# endif /* DEBUG */

  XP_FREE(object);
}


MimeObjectClass *
mime_find_class (const char *content_type, MimeHeaders *hdrs,
				 MimeDisplayOptions *opts, XP_Bool exact_match_p)
{
  MimeObjectClass *class = 0;

  if (!content_type || !*content_type ||
	  !strcasecomp(content_type, "text"))  /* with no / in the type */
	class = (MimeObjectClass *)&mimeUntypedTextClass;

  /* Subtypes of text...
   */
  else if (!strncasecomp(content_type,			"text/", 5))
	{
	  if      (!strcasecomp(content_type+5,		"html"))
		class = (MimeObjectClass *)&mimeInlineTextHTMLClass;
	  else if (!strcasecomp(content_type+5,		"enriched"))
		class = (MimeObjectClass *)&mimeInlineTextEnrichedClass;
	  else if (!strcasecomp(content_type+5,		"richtext"))
		class = (MimeObjectClass *)&mimeInlineTextRichtextClass;
	  else if (!strcasecomp(content_type+5,		"plain"))
		class = (MimeObjectClass *)&mimeInlineTextPlainClass;
#ifndef MOZILLA_30
	  else if (!strcasecomp(content_type+5,		"x-vcard"))
		class = (MimeObjectClass *)&mimeInlineTextVCardClass;
#endif /* !MOZILLA_30 */
	  else if (!exact_match_p)
		class = (MimeObjectClass *)&mimeInlineTextPlainClass;
	}

  /* Subtypes of multipart...
   */
  else if (!strncasecomp(content_type,			"multipart/", 10))
	{
	  if      (!strcasecomp(content_type+10,	"alternative"))
		class = (MimeObjectClass *)&mimeMultipartAlternativeClass;
	  else if (!strcasecomp(content_type+10,	"related"))
		class = (MimeObjectClass *)&mimeMultipartRelatedClass;
	  else if (!strcasecomp(content_type+10,	"digest"))
		class = (MimeObjectClass *)&mimeMultipartDigestClass;
	  else if (!strcasecomp(content_type+10,	"appledouble") ||
			   !strcasecomp(content_type+10,	"header-set"))
		class = (MimeObjectClass *)&mimeMultipartAppleDoubleClass;
	  else if (!strcasecomp(content_type+10,	"parallel"))
		class = (MimeObjectClass *)&mimeMultipartParallelClass;
      else if (!strcasecomp(content_type+10,	"mixed"))
		class = (MimeObjectClass *)&mimeMultipartMixedClass;

#ifndef NO_SECURITY
	  else if (!strcasecomp(content_type+10,	"signed"))
        class = mime_find_signed_class_from_proto(hdrs);
#endif /* NO_SECURITY */

	  if (!class && !exact_match_p)
		/* Treat all unknown multipart subtypes as "multipart/mixed" */
		class = (MimeObjectClass *)&mimeMultipartMixedClass;
	}

  /* Subtypes of message...
   */
  else if (!strncasecomp(content_type,			"message/", 8))
	{
	  if      (!strcasecomp(content_type+8,		"rfc822") ||
			   !strcasecomp(content_type+8,		"news"))
		class = (MimeObjectClass *)&mimeMessageClass;
	  else if (!strcasecomp(content_type+8,		"external-body"))
		class = (MimeObjectClass *)&mimeExternalBodyClass;
	  else if (!strcasecomp(content_type+8,		"partial"))
		/* I guess these are most useful as externals, for now... */
		class = (MimeObjectClass *)&mimeExternalObjectClass;
	  else if (!exact_match_p)
		/* Treat all unknown message subtypes as "text/plain" */
		class = (MimeObjectClass *)&mimeInlineTextPlainClass;
	}

  /* The magic image types which we are able to display internally...
   */
  else if (!strcasecomp(content_type,			IMAGE_GIF)  ||
		   !strcasecomp(content_type,			IMAGE_JPG) ||
		   !strcasecomp(content_type,			IMAGE_PJPG) ||
           	   !strcasecomp(content_type,			IMAGE_PNG) ||
		   !strcasecomp(content_type,			IMAGE_XBM)  ||
		   !strcasecomp(content_type,			IMAGE_XBM2) ||
		   !strcasecomp(content_type,			IMAGE_XBM3))
	class = (MimeObjectClass *)&mimeInlineImageClass;

  /* A few types which occur in the real world and which we would otherwise
	 treat as non-text types (which would be bad) without this special-case...
   */
  else if (!strcasecomp(content_type,			APPLICATION_PGP) ||
		   !strcasecomp(content_type,			APPLICATION_PGP2))
	class = (MimeObjectClass *)&mimeInlineTextPlainClass;

  else if (!strcasecomp(content_type,			SUN_ATTACHMENT))
	class = (MimeObjectClass *)&mimeSunAttachmentClass;

#ifndef NO_SECURITY
  /* check for other, possibly encrypted, types */
  else if ((class = mime_find_class_from_content_type(content_type)) != 0)
    ;
#endif /* NO_SECURITY */

  /* Everything else gets represented as a clickable link.
   */
  else if (!exact_match_p)
	class = (MimeObjectClass *)&mimeExternalObjectClass;

 if (!exact_match_p)
   XP_ASSERT(class);
  if (!class) return 0;

  /* If the `Show Attachments as Links' kludge is on, now would be the time
	 to change our mind... */
  if (opts && opts->no_inline_p)
	{
	  if (mime_subclass_p(class, (MimeObjectClass *)&mimeInlineTextClass))
		{
		  /* It's a text type.  Write it only if it's the *first* part
			 that we're writing, and then only if it has no "filename"
			 specified (the assumption here being, if it has a filename,
			 it wasn't simply typed into the text field -- it was actually
			 an attached document.) */
		  if (opts->state && opts->state->first_part_written_p)
			class = (MimeObjectClass *)&mimeExternalObjectClass;
		  else
			{
			  /* If there's a name, then write this as an attachment. */
			  char *name = (hdrs ? MimeHeaders_get_name(hdrs) : 0);
			  if (name)
				class = (MimeObjectClass *)&mimeExternalObjectClass;
			  FREEIF(name);
			}

		  if (opts->state)
			opts->state->first_part_written_p = TRUE;
		}
	  else if (mime_subclass_p(class,(MimeObjectClass *)&mimeContainerClass) &&
			   !mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
		/* Multipart subtypes are ok, except for messages; descend into
		   multiparts, and defer judgement.
		 */
		;
	  else if (opts &&
			   opts->part_to_load &&
			   mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
		/* Descend into messages only if we're looking for a specific sub-part.
		 */
		;
	  else
		{
		  /* Anything else, and display it as a link (and cause subsequent
			 text parts to also be displayed as links.) */
		  class = (MimeObjectClass *)&mimeExternalObjectClass;
		  if (opts->state)
			opts->state->first_part_written_p = TRUE;
		}
	}

  XP_ASSERT(class);

  if (class && !class->class_initialized)
	{
	  int status = mime_classinit(class);
	  if (status < 0) return 0;
	}

  return class;
}


MimeObject *
mime_create (const char *content_type, MimeHeaders *hdrs,
			 MimeDisplayOptions *opts)
{
  /* If there is no Content-Disposition header, or if the Content-Disposition
	 is ``inline'', then we display the part inline (and let mime_find_class()
	 decide how.)

	 If there is any other Content-Disposition (either ``attachment'' or some
	 disposition that we don't recognise) then we always display the part as
	 an external link, by using MimeExternalObject to display it.

	 But Content-Disposition is ignored for all containers except `message'.
	 (including multipart/mixed, and multipart/digest.)  It's not clear if
	 this is to spec, but from a usability standpoint, I think it's necessary.
   */

  MimeObjectClass *class = 0;
  char *content_disposition = 0;
  MimeObject *obj = 0;
  char *override_content_type = 0;
  static XP_Bool reverse_lookup = FALSE, got_lookup_pref = FALSE;

  if (!got_lookup_pref)
  {
#ifndef MOZILLA_30
	  PREF_GetBoolPref("mailnews.autolookup_unknown_mime_types",&reverse_lookup);
#endif
	  got_lookup_pref = TRUE;
  }


  /* There are some clients send out all attachments with a content-type
	 of application/octet-stream.  So, if we have an octet-stream attachment,
	 try to guess what type it really is based on the file extension.  I HATE
	 that we have to do this...

     If the preference "mailnews.autolookup_unknown_mime_types" is set to TRUE,
     then we try to do this EVERY TIME when we do not have an entry for the given
	 MIME type in our table, not only when it's application/octet-stream. */
  if (hdrs && opts && opts->file_type_fn &&

	  /* ### mwelch - don't override AppleSingle */
	  (content_type ? strcasecomp(content_type, APPLICATION_APPLEFILE) : TRUE) &&
	  /* ## davidm Apple double shouldn't use this #$%& either. */
	  (content_type ? strcasecomp(content_type, MULTIPART_APPLEDOUBLE) : TRUE) &&
	  (!content_type ||
	   !strcasecomp(content_type, APPLICATION_OCTET_STREAM) ||
	   !strcasecomp(content_type, UNKNOWN_CONTENT_TYPE) ||
	   (reverse_lookup &&
	    !NET_cinfo_find_info_by_type((char*)content_type))))
	{
	  char *name = MimeHeaders_get_name(hdrs);
	  if (name)
		{
		  override_content_type = opts->file_type_fn (name,
													  opts->stream_closure);
		  FREEIF(name);

		  if (override_content_type &&
			  !strcasecomp(override_content_type, UNKNOWN_CONTENT_TYPE))
			FREEIF(override_content_type);

		  if (override_content_type)
			content_type = override_content_type;
		}
	}


  class = mime_find_class(content_type, hdrs, opts, FALSE);

  XP_ASSERT(class);
  if (!class) goto FAIL;

  if (opts && opts->part_to_load)
	/* Always ignore Content-Disposition when we're loading some specific
	   sub-part (which may be within some container that we wouldn't otherwise
	   descend into, if the container itself had a Content-Disposition of
	   `attachment'. */
	content_disposition = 0;

  else if (mime_subclass_p(class,(MimeObjectClass *)&mimeContainerClass) &&
		   !mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
	/* Ignore Content-Disposition on all containers except `message'.
	   That is, Content-Disposition is ignored for multipart/mixed objects,
	   but is obeyed for message/rfc822 objects. */
	content_disposition = 0;

  else
  {
	/* change content-Disposition for vcards to be inline so */
	/* we can see a nice html display */
#ifndef MOZILLA_30
	if (mime_subclass_p(class,(MimeObjectClass *)&mimeInlineTextVCardClass))
		StrAllocCopy(content_disposition, "inline");
	else
#endif /* !MOZILLA_30 */
		content_disposition = (hdrs
							   ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION,
												 TRUE, FALSE)
							   : 0);
  }

  if (!content_disposition ||
	  !strcasecomp(content_disposition, "inline"))
	;	/* Use the class we've got. */
  else
	class = (MimeObjectClass *)&mimeExternalObjectClass;

  FREEIF(content_disposition);
  obj = mime_new (class, hdrs, content_type);

 FAIL:

  /* If we decided to ignore the content-type in the headers of this object
	 (see above) then make sure that our new content-type is stored in the
	 object itself.  (Or free it, if we're in an out-of-memory situation.)
   */
  if (override_content_type)
	{
	  if (obj)
		{
		  FREEIF(obj->content_type);
		  obj->content_type = override_content_type;
		}
	  else
		{
		  XP_FREE(override_content_type);
		}
	}

  return obj;
}



static int mime_classinit_1(MimeObjectClass *class, MimeObjectClass *target);

static int
mime_classinit(MimeObjectClass *class)
{
  int status;
  if (class->class_initialized)
	return 0;

  XP_ASSERT(class->class_initialize);
  if (!class->class_initialize)
	return -1;

  /* First initialize the superclass.
   */
  if (class->superclass && !class->superclass->class_initialized)
	{
	  status = mime_classinit(class->superclass);
	  if (status < 0) return status;
	}

  /* Now run each of the superclass-init procedures in turn,
	 parentmost-first. */
  status = mime_classinit_1(class, class);
  if (status < 0) return status;

  /* Now we're done. */
  class->class_initialized = TRUE;
  return 0;
}

static int
mime_classinit_1(MimeObjectClass *class, MimeObjectClass *target)
{
  int status;
  if (class->superclass)
	{
	  status = mime_classinit_1(class->superclass, target);
	  if (status < 0) return status;
	}
  return class->class_initialize(target);
}


XP_Bool
mime_subclass_p(MimeObjectClass *child, MimeObjectClass *parent)
{
  if (child == parent)
	return TRUE;
  else if (!child->superclass)
	return FALSE;
  else
	return mime_subclass_p(child->superclass, parent);
}

XP_Bool
mime_typep(MimeObject *obj, MimeObjectClass *class)
{
  return mime_subclass_p(obj->class, class);
}



/* URL munging
 */


/* Returns a string describing the location of the part (like "2.5.3").
   This is not a full URL, just a part-number.
 */
char *
mime_part_address(MimeObject *obj)
{
  if (!obj->parent)
	return XP_STRDUP("0");
  else
	{
	  /* Find this object in its parent. */
	  int32 i, j = -1;
	  char buf [20];
	  char *higher = 0;
	  MimeContainer *cont = (MimeContainer *) obj->parent;
	  XP_ASSERT(mime_typep(obj->parent,
						   (MimeObjectClass *)&mimeContainerClass));
	  for (i = 0; i < cont->nchildren; i++)
		if (cont->children[i] == obj)
		  {
			j = i+1;
			break;
		  }
	  if (j == -1)
		{
		  XP_ASSERT(0);
		  return 0;
		}

	  XP_SPRINTF(buf, "%ld", j);
	  if (obj->parent->parent)
		{
		  higher = mime_part_address(obj->parent);
		  if (!higher) return 0;  /* MK_OUT_OF_MEMORY */
		}

	  if (!higher)
		return XP_STRDUP(buf);
	  else
		{
		  char *s = XP_ALLOC(XP_STRLEN(higher) + XP_STRLEN(buf) + 3);
		  if (!s)
			{
			  XP_FREE(higher);
			  return 0;  /* MK_OUT_OF_MEMORY */
			}
		  XP_STRCPY(s, higher);
		  XP_STRCAT(s, ".");
		  XP_STRCAT(s, buf);
		  XP_FREE(higher);
		  return s;
		}
	}
}


/* Asks whether the given object is one of the cryptographically signed
   or encrypted objects that we know about.  (MimeMessageClass uses this
   to decide if the headers need to be presented differently.)
 */
XP_Bool
mime_crypto_object_p(MimeHeaders *hdrs, XP_Bool clearsigned_counts)
{
  char *ct;
  MimeObjectClass *class;

  if (!hdrs) return FALSE;

  ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, TRUE, FALSE);
  if (!ct) return FALSE;

  /* Rough cut -- look at the string before doing a more complex comparison. */
  if (strcasecomp(ct, MULTIPART_SIGNED) &&
	  strncasecomp(ct, "application/", 12))
	{
	  XP_FREE(ct);
	  return FALSE;
	}

  /* It's a candidate for being a crypto object.  Let's find out for sure... */
  class = mime_find_class (ct, hdrs, 0, TRUE);
  XP_FREE(ct);

#ifndef NO_SECURITY
  return mime_is_sec_class_p(class);
#endif /* NO_SECURITY */
}


/* Whether the given object has written out the HTML version of its headers
   in such a way that it will have a "crypto stamp" next to the headers.  If
   this is true, then the child must write out its HTML slightly differently
   to take this into account...
 */
XP_Bool
mime_crypto_stamped_p(MimeObject *obj)
{
  XP_ASSERT(obj);
  if (!obj) return FALSE;
  if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass))
	return ((MimeMessage *) obj)->crypto_stamped_p;
  else
	return FALSE;
}



/* How the crypto code tells the MimeMessage object what the crypto stamp
   on it says. */
void
mime_set_crypto_stamp(MimeObject *obj, XP_Bool signed_p, XP_Bool encrypted_p)
{
  if (!obj) return;
  if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass))
	{
	  MimeMessage *msg = (MimeMessage *) obj;
	  if (!msg->crypto_msg_signed_p)
		msg->crypto_msg_signed_p = signed_p;
	  if (!msg->crypto_msg_encrypted_p)
		msg->crypto_msg_encrypted_p = encrypted_p;

	  /* If the `decrypt_p' option is on, record whether any decryption has
		 actually occurred. */
	  if (encrypted_p &&
		  obj->options &&
		  obj->options->decrypt_p &&
		  obj->options->state)
		{
		  /* decrypt_p and write_html_p are incompatible. */
		  XP_ASSERT(!obj->options->write_html_p);
		  obj->options->state->decrypted_p = TRUE;
		}

	  return;  /* continue up the tree?  I think that's not a good idea. */
	}

  if (obj->parent)
	mime_set_crypto_stamp(obj->parent, signed_p, encrypted_p);
}



/* Puts a part-number into a URL.  If append_p is true, then the part number
   is appended to any existing part-number already in that URL; otherwise,
   it replaces it.
 */
char *
mime_set_url_part(const char *url, char *part, XP_Bool append_p)
{
  const char *part_begin = 0;
  const char *part_end = 0;
  XP_Bool got_q = FALSE;
  const char *s;
  char *result;

  for (s = url; *s; s++)
	{
	  if (*s == '?')
		{
		  got_q = TRUE;
		  if (!strncasecomp(s, "?part=", 6))
			part_begin = (s += 6);
		}
	  else if (got_q && *s == '&' && !strncasecomp(s, "&part=", 6))
		part_begin = (s += 6);

	  if (part_begin)
		{
		  for (; (*s && *s != '?' && *s != '&'); s++)
			;
		  part_end = s;
		  break;
		}
	}

  result = (char *) XP_ALLOC(XP_STRLEN(url) + XP_STRLEN(part) + 10);
  if (!result) return 0;

  if (part_begin)
	{
	  if (append_p)
		{
		  XP_MEMCPY(result, url, part_end - url);
		  result [part_end - url]     = '.';
		  result [part_end - url + 1] = 0;
		}
	  else
		{
		  XP_MEMCPY(result, url, part_begin - url);
		  result [part_begin - url] = 0;
		}
	}
  else
	{
	  XP_STRCPY(result, url);
	  if (got_q)
		XP_STRCAT(result, "&part=");
	  else
		XP_STRCAT(result, "?part=");
	}

  XP_STRCAT(result, part);

  if (part_end && *part_end)
	XP_STRCAT(result, part_end);

  /* Semi-broken kludge to omit a trailing "?part=0". */
  {
	int L = XP_STRLEN(result);
	if (L > 6 &&
		(result[L-7] == '?' || result[L-7] == '&') &&
		!XP_STRCMP("part=0", result + L - 6))
	  result[L-7] = 0;
  }

  return result;
}


/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
   number matches, and returns the MimeObject (else NULL.)
   (part is not a URL -- it's of the form "1.3.5".)
 */
MimeObject *
mime_address_to_part(const char *part, MimeObject *obj)
{
  /* Note: this is an N^2 operation, but the number of parts in a message
	 shouldn't ever be large enough that this really matters... */

  XP_Bool match;

  if (!part || !*part)
	{
	  match = !obj->parent;
	}
  else
	{
	  char *part2 = mime_part_address(obj);
	  if (!part2) return 0;  /* MK_OUT_OF_MEMORY */
	  match = !XP_STRCMP(part, part2);
	  XP_FREE(part2);
	}

  if (match)
	{
	  /* These are the droids we're looking for. */
	  return obj;
	}
  else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass))
	{
	  /* Not a container, pull up, pull up! */
	  return 0;
	}
  else
	{
	  int32 i;
	  MimeContainer *cont = (MimeContainer *) obj;
	  for (i = 0; i < cont->nchildren; i++)
		{
		  MimeObject *o2 = mime_address_to_part(part, cont->children[i]);
		  if (o2) return o2;
		}
	  return 0;
	}
}

/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
   number matches; if one is found, returns the Content-Name of that part.
   Else returns NULL.  (part is not a URL -- it's of the form "1.3.5".)
 */
char *
mime_find_content_type_of_part(const char *part, MimeObject *obj)
{
  char *result = 0;

  obj = mime_address_to_part(part, obj);
  if (!obj) return 0;

  result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, TRUE, FALSE) : 0);

  return result;
}

/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
   number matches; if one is found, returns the Content-Name of that part.
   Else returns NULL.  (part is not a URL -- it's of the form "1.3.5".)
 */
char *
mime_find_suggested_name_of_part(const char *part, MimeObject *obj)
{
  char *result = 0;

  obj = mime_address_to_part(part, obj);
  if (!obj) return 0;

  result = (obj->headers ? MimeHeaders_get_name(obj->headers) : 0);

  /* If this part doesn't have a name, but this part is one fork of an
	 AppleDouble, and the AppleDouble itself has a name, then use that. */
  if (!result &&
	  obj->parent &&
	  obj->parent->headers &&
	  mime_typep(obj->parent,
				 (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
	result = MimeHeaders_get_name(obj->parent->headers);

  /* Else, if this part is itself an AppleDouble, and one of its children
	 has a name, then use that (check data fork first, then resource.) */
  if (!result &&
	  mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
	{
	  MimeContainer *cont = (MimeContainer *) obj;
	  if (cont->nchildren > 1 &&
		  cont->children[1] &&
		  cont->children[1]->headers)
		result = MimeHeaders_get_name(cont->children[1]->headers);

	  if (!result &&
		  cont->nchildren > 0 &&
		  cont->children[0] &&
		  cont->children[0]->headers)
		result = MimeHeaders_get_name(cont->children[0]->headers);
	}

  /* Ok, now we have the suggested name, if any.
	 Now we remove any extensions that correspond to the
	 Content-Transfer-Encoding.  For example, if we see the headers

		Content-Type: text/plain
		Content-Disposition: inline; filename=foo.text.uue
		Content-Transfer-Encoding: x-uuencode

	 then we would look up (in mime.types) the file extensions which are
	 associated with the x-uuencode encoding, find that "uue" is one of
	 them, and remove that from the end of the file name, thus returning
	 "foo.text" as the name.  This is because, by the time this file ends
	 up on disk, its content-transfer-encoding will have been removed;
	 therefore, we should suggest a file name that indicates that.
   */
  if (result && obj->encoding && *obj->encoding)
	{
	  int32 L = XP_STRLEN(result);
	  const char **exts = 0;

	  /*
		 I'd like to ask the mime.types file, "what extensions correspond
		 to obj->encoding (which happens to be "x-uuencode") but doing that
		 in a non-sphagetti way would require brain surgery.  So, since
		 currently uuencode is the only content-transfer-encoding which we
		 understand which traditionally has an extension, we just special-
		 case it here!  Icepicks in my forehead!

		 Note that it's special-cased in a similar way in libmsg/compose.c.
	   */
	  if (!strcasecomp(obj->encoding, ENCODING_UUENCODE))
		{
		  static const char *uue_exts[] = { "uu", "uue", 0 };
		  exts = uue_exts;
		}

	  while (exts && *exts)
		{
		  const char *ext = *exts;
		  int32 L2 = XP_STRLEN(ext);
		  if (L > L2 + 1 &&							/* long enough */
			  result[L - L2 - 1] == '.' &&			/* '.' in right place*/
			  !strcasecomp(ext, result + (L - L2)))	/* ext matches */
			{
			  result[L - L2 - 1] = 0;		/* truncate at '.' and stop. */
			  break;
			}
		  exts++;
		}
	}

  return result;
}



/* Parse the various "?" options off the URL and into the options struct.
 */
int
mime_parse_url_options(const char *url, MimeDisplayOptions *options)
{
  const char *q;
  MimeHeadersState default_headers = options->headers;

  if (!url || !*url) return 0;
  if (!options) return 0;

  q = XP_STRRCHR (url, '?');
  if (! q) return 0;
  q++;
  while (*q)
	{
	  const char *end, *value, *name_end;
	  for (end = q; *end && *end != '&'; end++)
		;
	  for (value = q; *value != '=' && value < end; value++)
		;
	  name_end = value;
	  if (value < end) value++;
	  if (name_end <= q)
		;
	  else if (!strncasecomp ("headers", q, name_end - q))
		{
		  if (end > value && !strncasecomp ("all", value, end - value))
			options->headers = MimeHeadersAll;
		  else if (end > value && !strncasecomp ("some", value, end - value))
			options->headers = MimeHeadersSome;
		  else if (end > value && !strncasecomp ("micro", value, end - value))
			options->headers = MimeHeadersMicro;
		  else if (end > value && !strncasecomp ("cite", value, end - value))
			options->headers = MimeHeadersCitation;
		  else if (end > value && !strncasecomp ("citation", value, end-value))
			options->headers = MimeHeadersCitation;
		  else
			options->headers = default_headers;
		}
	  else if (!strncasecomp ("part", q, name_end - q))
		{
		  FREEIF (options->part_to_load);
		  if (end > value)
			{
			  options->part_to_load = (char *) XP_ALLOC(end - value + 1);
			  if (!options->part_to_load)
				return MK_OUT_OF_MEMORY;
			  XP_MEMCPY(options->part_to_load, value, end-value);
			  options->part_to_load[end-value] = 0;
			}
		}
	  else if (!strncasecomp ("rot13", q, name_end - q))
		{
		  if (end <= value || !strncasecomp ("true", value, end - value))
			options->rot13_p = TRUE;
		  else
			options->rot13_p = FALSE;
		}
	  else if (!strncasecomp ("inline", q, name_end - q))
		{
		  if (end <= value || !strncasecomp ("true", value, end - value))
			options->no_inline_p = FALSE;
		  else
			options->no_inline_p = TRUE;
		}

	  q = end;
	  if (*q)
		q++;
	}


  /* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0)
	 MIME parser.

	 Basically, the problem is that the old part-numbering code was totally
	 busted: here's a comparison of the old and new numberings with a pair
	 of hypothetical messages (one with a single part, and one with nested
	 containers.)
                               NEW:      OLD:  OR:
         message/rfc822
           image/jpeg           1         0     0

         message/rfc822
           multipart/mixed      1         0     0
             text/plain         1.1       1     1
             image/jpeg         1.2       2     2
             message/rfc822     1.3       -     3
               text/plain       1.3.1     3     -
             message/rfc822     1.4       -     4
               multipart/mixed  1.4.1     4     -
                 text/plain     1.4.1.1   4.1   -
                 image/jpeg     1.4.1.2   4.2   -
             text/plain         1.5       5     5

	 The "NEW" column is how the current code counts.  The "OLD" column is
	 what "?part=" references would do in 3.0b4 and earlier; you'll see that
	 you couldn't directly refer to the child message/rfc822 objects at all!
	 But that's when it got really weird, because if you turned on
	 "Attachments As Links" (or used a URL like "?inline=false&part=...")
	 then you got a totally different numbering system (seen in the "OR"
	 column.)  Gag!

	 So, the problem is, ClariNet had been using these part numbers in their
	 HTML news feeds, as a sleazy way of transmitting both complex HTML layouts
	 and images using NNTP as transport, without invoking HTTP.

	 The following clause is to provide some small amount of backward
	 compatibility.  By looking at that table, one can see that in the new
	 model, "part=0" has no meaning, and neither does "part=2" or "part=3"
	 and so on.

     "part=1" is ambiguous between the old and new way, as is any part
	 specification that has a "." in it.

	 So, the compatibility hack we do here is: if the part is "0", then map
	 that to "1".  And if the part is >= "2", then prepend "1." to it (so that
	 we map "2" to "1.2", and "3" to "1.3".)

	 This leaves the URLs compatible in the cases of:

	   = single part messages
	   = references to elements of a top-level multipart except the first

     and leaves them incompatible for:

	   = the first part of a top-level multipart
	   = all elements deeper than the outermost part

	 Life s#$%s when you don't properly think out things that end up turning
	 into de-facto standards...
   */
  if (options->part_to_load &&
	  !XP_STRCHR(options->part_to_load, '.'))		/* doesn't contain a dot */
	{
	  if (!XP_STRCMP(options->part_to_load, "0"))		/* 0 */
		{
		  XP_FREE(options->part_to_load);
		  options->part_to_load = XP_STRDUP("1");
		  if (!options->part_to_load)
			return MK_OUT_OF_MEMORY;
		}
	  else if (XP_STRCMP(options->part_to_load, "1"))	/* not 1 */
		{
		  const char *prefix = "1.";
		  char *s = (char *) XP_ALLOC(XP_STRLEN(options->part_to_load) +
									  XP_STRLEN(prefix) + 1);
		  if (!s) return MK_OUT_OF_MEMORY;
		  XP_STRCPY(s, prefix);
		  XP_STRCAT(s, options->part_to_load);
		  XP_FREE(options->part_to_load);
		  options->part_to_load = s;
		}
	}


  return 0;
}


/* Some output-generation utility functions...
 */

int
MimeOptions_write(MimeDisplayOptions *opt, char *data, int32 length,
				  XP_Bool user_visible_p)
{
  int status = 0;
  void* closure = 0;
  if (!opt || !opt->output_fn || !opt->state)
	return 0;

  closure = opt->output_closure;
  if (!closure) closure = opt->stream_closure;

  XP_ASSERT(opt->state->first_data_written_p);

  if (opt->state->separator_queued_p && user_visible_p)
	{
	  opt->state->separator_queued_p = FALSE;
	  if (opt->state->separator_suppressed_p)
		opt->state->separator_suppressed_p = FALSE;
	  else
		{
		  char sep[] = "<HR WIDTH=\"90%\" SIZE=4>";
		  int status = opt->output_fn(sep, XP_STRLEN(sep), closure);
		  opt->state->separator_suppressed_p = FALSE;
		  if (status < 0) return status;
		}
	}
  if (user_visible_p)
	opt->state->separator_suppressed_p = FALSE;

  if (length > 0)
	{
	  status = opt->output_fn(data, length, closure);
	  if (status < 0) return status;
	}

  return 0;
}

int
MimeObject_write(MimeObject *obj, char *output, int32 length,
				 XP_Bool user_visible_p)
{
  if (!obj->output_p) return 0;

  if (!obj->options->state->first_data_written_p)
	{
	  int status = MimeObject_output_init(obj, 0);
	  if (status < 0) return status;
	  XP_ASSERT(obj->options->state->first_data_written_p);
	}

  return MimeOptions_write(obj->options, output, length, user_visible_p);
}

int
MimeObject_write_separator(MimeObject *obj)
{
  if (obj->options && obj->options->state)
	obj->options->state->separator_queued_p = TRUE;
  return 0;
}

int
MimeObject_output_init(MimeObject *obj, const char *content_type)
{
  if (obj &&
	  obj->options &&
	  obj->options->state &&
	  !obj->options->state->first_data_written_p)
	{
	  int status;
	  const char *charset = 0;
	  char *name = 0, *x_mac_type = 0, *x_mac_creator = 0;

	  if (!obj->options->output_init_fn)
		{
		  obj->options->state->first_data_written_p = TRUE;
		  return 0;
		}

	  if (obj->headers)
		{
		  char *ct;
		  name = MimeHeaders_get_name(obj->headers);

		  ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE,
							   FALSE, FALSE);
		  if (ct)
			{
			  x_mac_type   = MimeHeaders_get_parameter(ct,PARAM_X_MAC_TYPE);
			  x_mac_creator= MimeHeaders_get_parameter(ct,PARAM_X_MAC_CREATOR);
			  FREEIF(obj->options->default_charset);
			  obj->options->default_charset = MimeHeaders_get_parameter(ct, "charset");
			  XP_FREE(ct);
			}
		}

	  if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass))
		charset = ((MimeInlineText *)obj)->charset;

	  if (!content_type)
		content_type = obj->content_type;
	  if (!content_type)
		content_type = TEXT_PLAIN;

	  status = obj->options->output_init_fn (content_type, charset, name,
											 x_mac_type, x_mac_creator,
											 obj->options->stream_closure);
	  FREEIF(name);
	  FREEIF(x_mac_type);
	  FREEIF(x_mac_creator);
	  obj->options->state->first_data_written_p = TRUE;
	  return status;
	}
  return 0;
}
