home *** CD-ROM | disk | FTP | other *** search
- /* -*- 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;
- }
-