home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libmime / mimei.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  34.3 KB  |  1,162 lines

  1. /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. /* mimei.c --- MIME parser, version 2 (see mimei.h).
  20.    Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96.
  21.  */
  22.  
  23. #include "mimeobj.h"    /*  MimeObject (abstract)                            */
  24. #include "mimecont.h"    /*   |--- MimeContainer (abstract)                    */
  25. #include "mimemult.h"    /*   |     |--- MimeMultipart (abstract)            */
  26. #include "mimemmix.h"    /*   |     |     |--- MimeMultipartMixed            */
  27. #include "mimemdig.h"    /*   |     |     |--- MimeMultipartDigest            */
  28. #include "mimempar.h"    /*   |     |     |--- MimeMultipartParallel            */
  29. #include "mimemalt.h"    /*   |     |     |--- MimeMultipartAlternative        */
  30. #include "mimemrel.h"    /*   |     |     |--- MimeMultipartRelated            */
  31. #include "mimemapl.h"    /*   |     |     |--- MimeMultipartAppleDouble        */
  32. #include "mimesun.h"    /*   |     |     |--- MimeSunAttachment                */
  33. #include "mimemsig.h"    /*   |     |     |--- MimeMultipartSigned (abstract)*/
  34. #include "mimemsg.h"    /*   |     |--- MimeMessage                            */
  35. #include "mimeunty.h"    /*   |     |--- MimeUntypedText                        */
  36. #include "mimeleaf.h"    /*   |--- MimeLeaf (abstract)                        */
  37. #include "mimetext.h"    /*   |     |--- MimeInlineText (abstract)            */
  38. #include "mimetpla.h"    /*   |     |     |--- MimeInlineTextPlain            */
  39. #include "mimethtm.h"    /*   |     |     |--- MimeInlineTextHTML            */
  40. #include "mimetric.h"    /*   |     |     |--- MimeInlineTextRichtext        */
  41. #include "mimetenr.h"    /*   |     |     |     |--- MimeInlineTextEnriched    */
  42. #ifndef MOZILLA_30
  43. #include "mimevcrd.h"   /*   |     |     |--------- MimeInlineTextVCard        */
  44. #include "prefapi.h"
  45. #endif /* !MOZILLA_30 */
  46. #include "mimeiimg.h"    /*   |     |--- MimeInlineImage                        */
  47. #include "mimeeobj.h"    /*   |     |--- MimeExternalObject                    */
  48. #include "mimeebod.h"    /*   |--- MimeExternalBody                            */
  49.  
  50. #ifndef NO_SECURITY
  51. #include "mimesec.h"
  52. #endif /* NO_SECURITY */
  53.  
  54. /* ==========================================================================
  55.    Allocation and destruction
  56.    ==========================================================================
  57.  */
  58.  
  59. static int mime_classinit(MimeObjectClass *class);
  60.  
  61. MimeObject *
  62. mime_new (MimeObjectClass *class, MimeHeaders *hdrs,
  63.           const char *override_content_type)
  64. {
  65.   int size = class->instance_size;
  66.   MimeObject *object;
  67.   int status;
  68.  
  69.   /* Some assertions to verify that this isn't random junk memory... */
  70.   XP_ASSERT(class->class_name && XP_STRLEN(class->class_name) > 0);
  71.   XP_ASSERT(size > 0 && size < 1000);
  72.  
  73.   if (!class->class_initialized)
  74.     {
  75.       status = mime_classinit(class);
  76.       if (status < 0) return 0;
  77.     }
  78.  
  79.   XP_ASSERT(class->initialize && class->finalize);
  80.  
  81.   if (hdrs)
  82.     {
  83.       hdrs = MimeHeaders_copy (hdrs);
  84.       if (!hdrs) return 0;
  85.     }
  86.  
  87.   class->showAttachmentIcon = FALSE; /* initialize ricardob's new member. */
  88.   object = (MimeObject *) XP_ALLOC(size);
  89.   if (!object) return 0;
  90.  
  91.   XP_MEMSET(object, 0, size);
  92.   object->class = class;
  93.   object->headers = hdrs;
  94.  
  95.   if (override_content_type && *override_content_type)
  96.     object->content_type = XP_STRDUP(override_content_type);
  97.  
  98.   status = class->initialize(object);
  99.   if (status < 0)
  100.     {
  101.       class->finalize(object);
  102.       XP_FREE(object);
  103.       return 0;
  104.     }
  105.  
  106.   return object;
  107. }
  108.  
  109. void
  110. mime_free (MimeObject *object)
  111. {
  112. # ifdef DEBUG__
  113.   int i, size = object->class->instance_size;
  114.   uint32 *array = (uint32*) object;
  115. # endif /* DEBUG */
  116.  
  117.   object->class->finalize(object);
  118.  
  119. # ifdef DEBUG__
  120.   for (i = 0; i < (size / sizeof(*array)); i++)
  121.     array[i] = (uint32) 0xDEADBEEF;
  122. # endif /* DEBUG */
  123.  
  124.   XP_FREE(object);
  125. }
  126.  
  127.  
  128. MimeObjectClass *
  129. mime_find_class (const char *content_type, MimeHeaders *hdrs,
  130.                  MimeDisplayOptions *opts, XP_Bool exact_match_p)
  131. {
  132.   MimeObjectClass *class = 0;
  133.  
  134.   if (!content_type || !*content_type ||
  135.       !strcasecomp(content_type, "text"))  /* with no / in the type */
  136.     class = (MimeObjectClass *)&mimeUntypedTextClass;
  137.  
  138.   /* Subtypes of text...
  139.    */
  140.   else if (!strncasecomp(content_type,            "text/", 5))
  141.     {
  142.       if      (!strcasecomp(content_type+5,        "html"))
  143.         class = (MimeObjectClass *)&mimeInlineTextHTMLClass;
  144.       else if (!strcasecomp(content_type+5,        "enriched"))
  145.         class = (MimeObjectClass *)&mimeInlineTextEnrichedClass;
  146.       else if (!strcasecomp(content_type+5,        "richtext"))
  147.         class = (MimeObjectClass *)&mimeInlineTextRichtextClass;
  148.       else if (!strcasecomp(content_type+5,        "plain"))
  149.         class = (MimeObjectClass *)&mimeInlineTextPlainClass;
  150. #ifndef MOZILLA_30
  151.       else if (!strcasecomp(content_type+5,        "x-vcard"))
  152.         class = (MimeObjectClass *)&mimeInlineTextVCardClass;
  153. #endif /* !MOZILLA_30 */
  154.       else if (!exact_match_p)
  155.         class = (MimeObjectClass *)&mimeInlineTextPlainClass;
  156.     }
  157.  
  158.   /* Subtypes of multipart...
  159.    */
  160.   else if (!strncasecomp(content_type,            "multipart/", 10))
  161.     {
  162.       if      (!strcasecomp(content_type+10,    "alternative"))
  163.         class = (MimeObjectClass *)&mimeMultipartAlternativeClass;
  164.       else if (!strcasecomp(content_type+10,    "related"))
  165.         class = (MimeObjectClass *)&mimeMultipartRelatedClass;
  166.       else if (!strcasecomp(content_type+10,    "digest"))
  167.         class = (MimeObjectClass *)&mimeMultipartDigestClass;
  168.       else if (!strcasecomp(content_type+10,    "appledouble") ||
  169.                !strcasecomp(content_type+10,    "header-set"))
  170.         class = (MimeObjectClass *)&mimeMultipartAppleDoubleClass;
  171.       else if (!strcasecomp(content_type+10,    "parallel"))
  172.         class = (MimeObjectClass *)&mimeMultipartParallelClass;
  173.       else if (!strcasecomp(content_type+10,    "mixed"))
  174.         class = (MimeObjectClass *)&mimeMultipartMixedClass;
  175.  
  176. #ifndef NO_SECURITY
  177.       else if (!strcasecomp(content_type+10,    "signed"))
  178.         class = mime_find_signed_class_from_proto(hdrs);
  179. #endif /* NO_SECURITY */
  180.  
  181.       if (!class && !exact_match_p)
  182.         /* Treat all unknown multipart subtypes as "multipart/mixed" */
  183.         class = (MimeObjectClass *)&mimeMultipartMixedClass;
  184.     }
  185.  
  186.   /* Subtypes of message...
  187.    */
  188.   else if (!strncasecomp(content_type,            "message/", 8))
  189.     {
  190.       if      (!strcasecomp(content_type+8,        "rfc822") ||
  191.                !strcasecomp(content_type+8,        "news"))
  192.         class = (MimeObjectClass *)&mimeMessageClass;
  193.       else if (!strcasecomp(content_type+8,        "external-body"))
  194.         class = (MimeObjectClass *)&mimeExternalBodyClass;
  195.       else if (!strcasecomp(content_type+8,        "partial"))
  196.         /* I guess these are most useful as externals, for now... */
  197.         class = (MimeObjectClass *)&mimeExternalObjectClass;
  198.       else if (!exact_match_p)
  199.         /* Treat all unknown message subtypes as "text/plain" */
  200.         class = (MimeObjectClass *)&mimeInlineTextPlainClass;
  201.     }
  202.  
  203.   /* The magic image types which we are able to display internally...
  204.    */
  205.   else if (!strcasecomp(content_type,            IMAGE_GIF)  ||
  206.            !strcasecomp(content_type,            IMAGE_JPG) ||
  207.            !strcasecomp(content_type,            IMAGE_PJPG) ||
  208.                   !strcasecomp(content_type,            IMAGE_PNG) ||
  209.            !strcasecomp(content_type,            IMAGE_XBM)  ||
  210.            !strcasecomp(content_type,            IMAGE_XBM2) ||
  211.            !strcasecomp(content_type,            IMAGE_XBM3))
  212.     class = (MimeObjectClass *)&mimeInlineImageClass;
  213.  
  214.   /* A few types which occur in the real world and which we would otherwise
  215.      treat as non-text types (which would be bad) without this special-case...
  216.    */
  217.   else if (!strcasecomp(content_type,            APPLICATION_PGP) ||
  218.            !strcasecomp(content_type,            APPLICATION_PGP2))
  219.     class = (MimeObjectClass *)&mimeInlineTextPlainClass;
  220.  
  221.   else if (!strcasecomp(content_type,            SUN_ATTACHMENT))
  222.     class = (MimeObjectClass *)&mimeSunAttachmentClass;
  223.  
  224. #ifndef NO_SECURITY
  225.   /* check for other, possibly encrypted, types */
  226.   else if ((class = mime_find_class_from_content_type(content_type)) != 0)
  227.     ;
  228. #endif /* NO_SECURITY */
  229.  
  230.   /* Everything else gets represented as a clickable link.
  231.    */
  232.   else if (!exact_match_p)
  233.     class = (MimeObjectClass *)&mimeExternalObjectClass;
  234.  
  235.  if (!exact_match_p)
  236.    XP_ASSERT(class);
  237.   if (!class) return 0;
  238.  
  239.   /* If the `Show Attachments as Links' kludge is on, now would be the time
  240.      to change our mind... */
  241.   if (opts && opts->no_inline_p)
  242.     {
  243.       if (mime_subclass_p(class, (MimeObjectClass *)&mimeInlineTextClass))
  244.         {
  245.           /* It's a text type.  Write it only if it's the *first* part
  246.              that we're writing, and then only if it has no "filename"
  247.              specified (the assumption here being, if it has a filename,
  248.              it wasn't simply typed into the text field -- it was actually
  249.              an attached document.) */
  250.           if (opts->state && opts->state->first_part_written_p)
  251.             class = (MimeObjectClass *)&mimeExternalObjectClass;
  252.           else
  253.             {
  254.               /* If there's a name, then write this as an attachment. */
  255.               char *name = (hdrs ? MimeHeaders_get_name(hdrs) : 0);
  256.               if (name)
  257.                 class = (MimeObjectClass *)&mimeExternalObjectClass;
  258.               FREEIF(name);
  259.             }
  260.  
  261.           if (opts->state)
  262.             opts->state->first_part_written_p = TRUE;
  263.         }
  264.       else if (mime_subclass_p(class,(MimeObjectClass *)&mimeContainerClass) &&
  265.                !mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
  266.         /* Multipart subtypes are ok, except for messages; descend into
  267.            multiparts, and defer judgement.
  268.          */
  269.         ;
  270.       else if (opts &&
  271.                opts->part_to_load &&
  272.                mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
  273.         /* Descend into messages only if we're looking for a specific sub-part.
  274.          */
  275.         ;
  276.       else
  277.         {
  278.           /* Anything else, and display it as a link (and cause subsequent
  279.              text parts to also be displayed as links.) */
  280.           class = (MimeObjectClass *)&mimeExternalObjectClass;
  281.           if (opts->state)
  282.             opts->state->first_part_written_p = TRUE;
  283.         }
  284.     }
  285.  
  286.   XP_ASSERT(class);
  287.  
  288.   if (class && !class->class_initialized)
  289.     {
  290.       int status = mime_classinit(class);
  291.       if (status < 0) return 0;
  292.     }
  293.  
  294.   return class;
  295. }
  296.  
  297.  
  298. MimeObject *
  299. mime_create (const char *content_type, MimeHeaders *hdrs,
  300.              MimeDisplayOptions *opts)
  301. {
  302.   /* If there is no Content-Disposition header, or if the Content-Disposition
  303.      is ``inline'', then we display the part inline (and let mime_find_class()
  304.      decide how.)
  305.  
  306.      If there is any other Content-Disposition (either ``attachment'' or some
  307.      disposition that we don't recognise) then we always display the part as
  308.      an external link, by using MimeExternalObject to display it.
  309.  
  310.      But Content-Disposition is ignored for all containers except `message'.
  311.      (including multipart/mixed, and multipart/digest.)  It's not clear if
  312.      this is to spec, but from a usability standpoint, I think it's necessary.
  313.    */
  314.  
  315.   MimeObjectClass *class = 0;
  316.   char *content_disposition = 0;
  317.   MimeObject *obj = 0;
  318.   char *override_content_type = 0;
  319.   static XP_Bool reverse_lookup = FALSE, got_lookup_pref = FALSE;
  320.  
  321.   if (!got_lookup_pref)
  322.   {
  323. #ifndef MOZILLA_30
  324.       PREF_GetBoolPref("mailnews.autolookup_unknown_mime_types",&reverse_lookup);
  325. #endif
  326.       got_lookup_pref = TRUE;
  327.   }
  328.  
  329.  
  330.   /* There are some clients send out all attachments with a content-type
  331.      of application/octet-stream.  So, if we have an octet-stream attachment,
  332.      try to guess what type it really is based on the file extension.  I HATE
  333.      that we have to do this...
  334.  
  335.      If the preference "mailnews.autolookup_unknown_mime_types" is set to TRUE,
  336.      then we try to do this EVERY TIME when we do not have an entry for the given
  337.      MIME type in our table, not only when it's application/octet-stream. */
  338.   if (hdrs && opts && opts->file_type_fn &&
  339.  
  340.       /* ### mwelch - don't override AppleSingle */
  341.       (content_type ? strcasecomp(content_type, APPLICATION_APPLEFILE) : TRUE) &&
  342.       /* ## davidm Apple double shouldn't use this #$%& either. */
  343.       (content_type ? strcasecomp(content_type, MULTIPART_APPLEDOUBLE) : TRUE) &&
  344.       (!content_type ||
  345.        !strcasecomp(content_type, APPLICATION_OCTET_STREAM) ||
  346.        !strcasecomp(content_type, UNKNOWN_CONTENT_TYPE) ||
  347.        (reverse_lookup &&
  348.         !NET_cinfo_find_info_by_type((char*)content_type))))
  349.     {
  350.       char *name = MimeHeaders_get_name(hdrs);
  351.       if (name)
  352.         {
  353.           override_content_type = opts->file_type_fn (name,
  354.                                                       opts->stream_closure);
  355.           FREEIF(name);
  356.  
  357.           if (override_content_type &&
  358.               !strcasecomp(override_content_type, UNKNOWN_CONTENT_TYPE))
  359.             FREEIF(override_content_type);
  360.  
  361.           if (override_content_type)
  362.             content_type = override_content_type;
  363.         }
  364.     }
  365.  
  366.  
  367.   class = mime_find_class(content_type, hdrs, opts, FALSE);
  368.  
  369.   XP_ASSERT(class);
  370.   if (!class) goto FAIL;
  371.  
  372.   if (opts && opts->part_to_load)
  373.     /* Always ignore Content-Disposition when we're loading some specific
  374.        sub-part (which may be within some container that we wouldn't otherwise
  375.        descend into, if the container itself had a Content-Disposition of
  376.        `attachment'. */
  377.     content_disposition = 0;
  378.  
  379.   else if (mime_subclass_p(class,(MimeObjectClass *)&mimeContainerClass) &&
  380.            !mime_subclass_p(class,(MimeObjectClass *)&mimeMessageClass))
  381.     /* Ignore Content-Disposition on all containers except `message'.
  382.        That is, Content-Disposition is ignored for multipart/mixed objects,
  383.        but is obeyed for message/rfc822 objects. */
  384.     content_disposition = 0;
  385.  
  386.   else
  387.   {
  388.     /* change content-Disposition for vcards to be inline so */
  389.     /* we can see a nice html display */
  390. #ifndef MOZILLA_30
  391.     if (mime_subclass_p(class,(MimeObjectClass *)&mimeInlineTextVCardClass))
  392.         StrAllocCopy(content_disposition, "inline");
  393.     else
  394. #endif /* !MOZILLA_30 */
  395.         content_disposition = (hdrs
  396.                                ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION,
  397.                                                  TRUE, FALSE)
  398.                                : 0);
  399.   }
  400.  
  401.   if (!content_disposition ||
  402.       !strcasecomp(content_disposition, "inline"))
  403.     ;    /* Use the class we've got. */
  404.   else
  405.     class = (MimeObjectClass *)&mimeExternalObjectClass;
  406.  
  407.   FREEIF(content_disposition);
  408.   obj = mime_new (class, hdrs, content_type);
  409.  
  410.  FAIL:
  411.  
  412.   /* If we decided to ignore the content-type in the headers of this object
  413.      (see above) then make sure that our new content-type is stored in the
  414.      object itself.  (Or free it, if we're in an out-of-memory situation.)
  415.    */
  416.   if (override_content_type)
  417.     {
  418.       if (obj)
  419.         {
  420.           FREEIF(obj->content_type);
  421.           obj->content_type = override_content_type;
  422.         }
  423.       else
  424.         {
  425.           XP_FREE(override_content_type);
  426.         }
  427.     }
  428.  
  429.   return obj;
  430. }
  431.  
  432.  
  433.  
  434. static int mime_classinit_1(MimeObjectClass *class, MimeObjectClass *target);
  435.  
  436. static int
  437. mime_classinit(MimeObjectClass *class)
  438. {
  439.   int status;
  440.   if (class->class_initialized)
  441.     return 0;
  442.  
  443.   XP_ASSERT(class->class_initialize);
  444.   if (!class->class_initialize)
  445.     return -1;
  446.  
  447.   /* First initialize the superclass.
  448.    */
  449.   if (class->superclass && !class->superclass->class_initialized)
  450.     {
  451.       status = mime_classinit(class->superclass);
  452.       if (status < 0) return status;
  453.     }
  454.  
  455.   /* Now run each of the superclass-init procedures in turn,
  456.      parentmost-first. */
  457.   status = mime_classinit_1(class, class);
  458.   if (status < 0) return status;
  459.  
  460.   /* Now we're done. */
  461.   class->class_initialized = TRUE;
  462.   return 0;
  463. }
  464.  
  465. static int
  466. mime_classinit_1(MimeObjectClass *class, MimeObjectClass *target)
  467. {
  468.   int status;
  469.   if (class->superclass)
  470.     {
  471.       status = mime_classinit_1(class->superclass, target);
  472.       if (status < 0) return status;
  473.     }
  474.   return class->class_initialize(target);
  475. }
  476.  
  477.  
  478. XP_Bool
  479. mime_subclass_p(MimeObjectClass *child, MimeObjectClass *parent)
  480. {
  481.   if (child == parent)
  482.     return TRUE;
  483.   else if (!child->superclass)
  484.     return FALSE;
  485.   else
  486.     return mime_subclass_p(child->superclass, parent);
  487. }
  488.  
  489. XP_Bool
  490. mime_typep(MimeObject *obj, MimeObjectClass *class)
  491. {
  492.   return mime_subclass_p(obj->class, class);
  493. }
  494.  
  495.  
  496.  
  497. /* URL munging
  498.  */
  499.  
  500.  
  501. /* Returns a string describing the location of the part (like "2.5.3").
  502.    This is not a full URL, just a part-number.
  503.  */
  504. char *
  505. mime_part_address(MimeObject *obj)
  506. {
  507.   if (!obj->parent)
  508.     return XP_STRDUP("0");
  509.   else
  510.     {
  511.       /* Find this object in its parent. */
  512.       int32 i, j = -1;
  513.       char buf [20];
  514.       char *higher = 0;
  515.       MimeContainer *cont = (MimeContainer *) obj->parent;
  516.       XP_ASSERT(mime_typep(obj->parent,
  517.                            (MimeObjectClass *)&mimeContainerClass));
  518.       for (i = 0; i < cont->nchildren; i++)
  519.         if (cont->children[i] == obj)
  520.           {
  521.             j = i+1;
  522.             break;
  523.           }
  524.       if (j == -1)
  525.         {
  526.           XP_ASSERT(0);
  527.           return 0;
  528.         }
  529.  
  530.       XP_SPRINTF(buf, "%ld", j);
  531.       if (obj->parent->parent)
  532.         {
  533.           higher = mime_part_address(obj->parent);
  534.           if (!higher) return 0;  /* MK_OUT_OF_MEMORY */
  535.         }
  536.  
  537.       if (!higher)
  538.         return XP_STRDUP(buf);
  539.       else
  540.         {
  541.           char *s = XP_ALLOC(XP_STRLEN(higher) + XP_STRLEN(buf) + 3);
  542.           if (!s)
  543.             {
  544.               XP_FREE(higher);
  545.               return 0;  /* MK_OUT_OF_MEMORY */
  546.             }
  547.           XP_STRCPY(s, higher);
  548.           XP_STRCAT(s, ".");
  549.           XP_STRCAT(s, buf);
  550.           XP_FREE(higher);
  551.           return s;
  552.         }
  553.     }
  554. }
  555.  
  556.  
  557. /* Asks whether the given object is one of the cryptographically signed
  558.    or encrypted objects that we know about.  (MimeMessageClass uses this
  559.    to decide if the headers need to be presented differently.)
  560.  */
  561. XP_Bool
  562. mime_crypto_object_p(MimeHeaders *hdrs, XP_Bool clearsigned_counts)
  563. {
  564.   char *ct;
  565.   MimeObjectClass *class;
  566.  
  567.   if (!hdrs) return FALSE;
  568.  
  569.   ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, TRUE, FALSE);
  570.   if (!ct) return FALSE;
  571.  
  572.   /* Rough cut -- look at the string before doing a more complex comparison. */
  573.   if (strcasecomp(ct, MULTIPART_SIGNED) &&
  574.       strncasecomp(ct, "application/", 12))
  575.     {
  576.       XP_FREE(ct);
  577.       return FALSE;
  578.     }
  579.  
  580.   /* It's a candidate for being a crypto object.  Let's find out for sure... */
  581.   class = mime_find_class (ct, hdrs, 0, TRUE);
  582.   XP_FREE(ct);
  583.  
  584. #ifndef NO_SECURITY
  585.   return mime_is_sec_class_p(class);
  586. #endif /* NO_SECURITY */
  587. }
  588.  
  589.  
  590. /* Whether the given object has written out the HTML version of its headers
  591.    in such a way that it will have a "crypto stamp" next to the headers.  If
  592.    this is true, then the child must write out its HTML slightly differently
  593.    to take this into account...
  594.  */
  595. XP_Bool
  596. mime_crypto_stamped_p(MimeObject *obj)
  597. {
  598.   XP_ASSERT(obj);
  599.   if (!obj) return FALSE;
  600.   if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass))
  601.     return ((MimeMessage *) obj)->crypto_stamped_p;
  602.   else
  603.     return FALSE;
  604. }
  605.  
  606.  
  607.  
  608. /* How the crypto code tells the MimeMessage object what the crypto stamp
  609.    on it says. */
  610. void
  611. mime_set_crypto_stamp(MimeObject *obj, XP_Bool signed_p, XP_Bool encrypted_p)
  612. {
  613.   if (!obj) return;
  614.   if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass))
  615.     {
  616.       MimeMessage *msg = (MimeMessage *) obj;
  617.       if (!msg->crypto_msg_signed_p)
  618.         msg->crypto_msg_signed_p = signed_p;
  619.       if (!msg->crypto_msg_encrypted_p)
  620.         msg->crypto_msg_encrypted_p = encrypted_p;
  621.  
  622.       /* If the `decrypt_p' option is on, record whether any decryption has
  623.          actually occurred. */
  624.       if (encrypted_p &&
  625.           obj->options &&
  626.           obj->options->decrypt_p &&
  627.           obj->options->state)
  628.         {
  629.           /* decrypt_p and write_html_p are incompatible. */
  630.           XP_ASSERT(!obj->options->write_html_p);
  631.           obj->options->state->decrypted_p = TRUE;
  632.         }
  633.  
  634.       return;  /* continue up the tree?  I think that's not a good idea. */
  635.     }
  636.  
  637.   if (obj->parent)
  638.     mime_set_crypto_stamp(obj->parent, signed_p, encrypted_p);
  639. }
  640.  
  641.  
  642.  
  643. /* Puts a part-number into a URL.  If append_p is true, then the part number
  644.    is appended to any existing part-number already in that URL; otherwise,
  645.    it replaces it.
  646.  */
  647. char *
  648. mime_set_url_part(const char *url, char *part, XP_Bool append_p)
  649. {
  650.   const char *part_begin = 0;
  651.   const char *part_end = 0;
  652.   XP_Bool got_q = FALSE;
  653.   const char *s;
  654.   char *result;
  655.  
  656.   for (s = url; *s; s++)
  657.     {
  658.       if (*s == '?')
  659.         {
  660.           got_q = TRUE;
  661.           if (!strncasecomp(s, "?part=", 6))
  662.             part_begin = (s += 6);
  663.         }
  664.       else if (got_q && *s == '&' && !strncasecomp(s, "&part=", 6))
  665.         part_begin = (s += 6);
  666.  
  667.       if (part_begin)
  668.         {
  669.           for (; (*s && *s != '?' && *s != '&'); s++)
  670.             ;
  671.           part_end = s;
  672.           break;
  673.         }
  674.     }
  675.  
  676.   result = (char *) XP_ALLOC(XP_STRLEN(url) + XP_STRLEN(part) + 10);
  677.   if (!result) return 0;
  678.  
  679.   if (part_begin)
  680.     {
  681.       if (append_p)
  682.         {
  683.           XP_MEMCPY(result, url, part_end - url);
  684.           result [part_end - url]     = '.';
  685.           result [part_end - url + 1] = 0;
  686.         }
  687.       else
  688.         {
  689.           XP_MEMCPY(result, url, part_begin - url);
  690.           result [part_begin - url] = 0;
  691.         }
  692.     }
  693.   else
  694.     {
  695.       XP_STRCPY(result, url);
  696.       if (got_q)
  697.         XP_STRCAT(result, "&part=");
  698.       else
  699.         XP_STRCAT(result, "?part=");
  700.     }
  701.  
  702.   XP_STRCAT(result, part);
  703.  
  704.   if (part_end && *part_end)
  705.     XP_STRCAT(result, part_end);
  706.  
  707.   /* Semi-broken kludge to omit a trailing "?part=0". */
  708.   {
  709.     int L = XP_STRLEN(result);
  710.     if (L > 6 &&
  711.         (result[L-7] == '?' || result[L-7] == '&') &&
  712.         !XP_STRCMP("part=0", result + L - 6))
  713.       result[L-7] = 0;
  714.   }
  715.  
  716.   return result;
  717. }
  718.  
  719.  
  720. /* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
  721.    number matches, and returns the MimeObject (else NULL.)
  722.    (part is not a URL -- it's of the form "1.3.5".)
  723.  */
  724. MimeObject *
  725. mime_address_to_part(const char *part, MimeObject *obj)
  726. {
  727.   /* Note: this is an N^2 operation, but the number of parts in a message
  728.      shouldn't ever be large enough that this really matters... */
  729.  
  730.   XP_Bool match;
  731.  
  732.   if (!part || !*part)
  733.     {
  734.       match = !obj->parent;
  735.     }
  736.   else
  737.     {
  738.       char *part2 = mime_part_address(obj);
  739.       if (!part2) return 0;  /* MK_OUT_OF_MEMORY */
  740.       match = !XP_STRCMP(part, part2);
  741.       XP_FREE(part2);
  742.     }
  743.  
  744.   if (match)
  745.     {
  746.       /* These are the droids we're looking for. */
  747.       return obj;
  748.     }
  749.   else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass))
  750.     {
  751.       /* Not a container, pull up, pull up! */
  752.       return 0;
  753.     }
  754.   else
  755.     {
  756.       int32 i;
  757.       MimeContainer *cont = (MimeContainer *) obj;
  758.       for (i = 0; i < cont->nchildren; i++)
  759.         {
  760.           MimeObject *o2 = mime_address_to_part(part, cont->children[i]);
  761.           if (o2) return o2;
  762.         }
  763.       return 0;
  764.     }
  765. }
  766.  
  767. /* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
  768.    number matches; if one is found, returns the Content-Name of that part.
  769.    Else returns NULL.  (part is not a URL -- it's of the form "1.3.5".)
  770.  */
  771. char *
  772. mime_find_content_type_of_part(const char *part, MimeObject *obj)
  773. {
  774.   char *result = 0;
  775.  
  776.   obj = mime_address_to_part(part, obj);
  777.   if (!obj) return 0;
  778.  
  779.   result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, TRUE, FALSE) : 0);
  780.  
  781.   return result;
  782. }
  783.  
  784. /* Given a part ID, looks through the MimeObject tree for a sub-part whose ID
  785.    number matches; if one is found, returns the Content-Name of that part.
  786.    Else returns NULL.  (part is not a URL -- it's of the form "1.3.5".)
  787.  */
  788. char *
  789. mime_find_suggested_name_of_part(const char *part, MimeObject *obj)
  790. {
  791.   char *result = 0;
  792.  
  793.   obj = mime_address_to_part(part, obj);
  794.   if (!obj) return 0;
  795.  
  796.   result = (obj->headers ? MimeHeaders_get_name(obj->headers) : 0);
  797.  
  798.   /* If this part doesn't have a name, but this part is one fork of an
  799.      AppleDouble, and the AppleDouble itself has a name, then use that. */
  800.   if (!result &&
  801.       obj->parent &&
  802.       obj->parent->headers &&
  803.       mime_typep(obj->parent,
  804.                  (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
  805.     result = MimeHeaders_get_name(obj->parent->headers);
  806.  
  807.   /* Else, if this part is itself an AppleDouble, and one of its children
  808.      has a name, then use that (check data fork first, then resource.) */
  809.   if (!result &&
  810.       mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
  811.     {
  812.       MimeContainer *cont = (MimeContainer *) obj;
  813.       if (cont->nchildren > 1 &&
  814.           cont->children[1] &&
  815.           cont->children[1]->headers)
  816.         result = MimeHeaders_get_name(cont->children[1]->headers);
  817.  
  818.       if (!result &&
  819.           cont->nchildren > 0 &&
  820.           cont->children[0] &&
  821.           cont->children[0]->headers)
  822.         result = MimeHeaders_get_name(cont->children[0]->headers);
  823.     }
  824.  
  825.   /* Ok, now we have the suggested name, if any.
  826.      Now we remove any extensions that correspond to the
  827.      Content-Transfer-Encoding.  For example, if we see the headers
  828.  
  829.         Content-Type: text/plain
  830.         Content-Disposition: inline; filename=foo.text.uue
  831.         Content-Transfer-Encoding: x-uuencode
  832.  
  833.      then we would look up (in mime.types) the file extensions which are
  834.      associated with the x-uuencode encoding, find that "uue" is one of
  835.      them, and remove that from the end of the file name, thus returning
  836.      "foo.text" as the name.  This is because, by the time this file ends
  837.      up on disk, its content-transfer-encoding will have been removed;
  838.      therefore, we should suggest a file name that indicates that.
  839.    */
  840.   if (result && obj->encoding && *obj->encoding)
  841.     {
  842.       int32 L = XP_STRLEN(result);
  843.       const char **exts = 0;
  844.  
  845.       /*
  846.          I'd like to ask the mime.types file, "what extensions correspond
  847.          to obj->encoding (which happens to be "x-uuencode") but doing that
  848.          in a non-sphagetti way would require brain surgery.  So, since
  849.          currently uuencode is the only content-transfer-encoding which we
  850.          understand which traditionally has an extension, we just special-
  851.          case it here!  Icepicks in my forehead!
  852.  
  853.          Note that it's special-cased in a similar way in libmsg/compose.c.
  854.        */
  855.       if (!strcasecomp(obj->encoding, ENCODING_UUENCODE))
  856.         {
  857.           static const char *uue_exts[] = { "uu", "uue", 0 };
  858.           exts = uue_exts;
  859.         }
  860.  
  861.       while (exts && *exts)
  862.         {
  863.           const char *ext = *exts;
  864.           int32 L2 = XP_STRLEN(ext);
  865.           if (L > L2 + 1 &&                            /* long enough */
  866.               result[L - L2 - 1] == '.' &&            /* '.' in right place*/
  867.               !strcasecomp(ext, result + (L - L2)))    /* ext matches */
  868.             {
  869.               result[L - L2 - 1] = 0;        /* truncate at '.' and stop. */
  870.               break;
  871.             }
  872.           exts++;
  873.         }
  874.     }
  875.  
  876.   return result;
  877. }
  878.  
  879.  
  880.  
  881. /* Parse the various "?" options off the URL and into the options struct.
  882.  */
  883. int
  884. mime_parse_url_options(const char *url, MimeDisplayOptions *options)
  885. {
  886.   const char *q;
  887.   MimeHeadersState default_headers = options->headers;
  888.  
  889.   if (!url || !*url) return 0;
  890.   if (!options) return 0;
  891.  
  892.   q = XP_STRRCHR (url, '?');
  893.   if (! q) return 0;
  894.   q++;
  895.   while (*q)
  896.     {
  897.       const char *end, *value, *name_end;
  898.       for (end = q; *end && *end != '&'; end++)
  899.         ;
  900.       for (value = q; *value != '=' && value < end; value++)
  901.         ;
  902.       name_end = value;
  903.       if (value < end) value++;
  904.       if (name_end <= q)
  905.         ;
  906.       else if (!strncasecomp ("headers", q, name_end - q))
  907.         {
  908.           if (end > value && !strncasecomp ("all", value, end - value))
  909.             options->headers = MimeHeadersAll;
  910.           else if (end > value && !strncasecomp ("some", value, end - value))
  911.             options->headers = MimeHeadersSome;
  912.           else if (end > value && !strncasecomp ("micro", value, end - value))
  913.             options->headers = MimeHeadersMicro;
  914.           else if (end > value && !strncasecomp ("cite", value, end - value))
  915.             options->headers = MimeHeadersCitation;
  916.           else if (end > value && !strncasecomp ("citation", value, end-value))
  917.             options->headers = MimeHeadersCitation;
  918.           else
  919.             options->headers = default_headers;
  920.         }
  921.       else if (!strncasecomp ("part", q, name_end - q))
  922.         {
  923.           FREEIF (options->part_to_load);
  924.           if (end > value)
  925.             {
  926.               options->part_to_load = (char *) XP_ALLOC(end - value + 1);
  927.               if (!options->part_to_load)
  928.                 return MK_OUT_OF_MEMORY;
  929.               XP_MEMCPY(options->part_to_load, value, end-value);
  930.               options->part_to_load[end-value] = 0;
  931.             }
  932.         }
  933.       else if (!strncasecomp ("rot13", q, name_end - q))
  934.         {
  935.           if (end <= value || !strncasecomp ("true", value, end - value))
  936.             options->rot13_p = TRUE;
  937.           else
  938.             options->rot13_p = FALSE;
  939.         }
  940.       else if (!strncasecomp ("inline", q, name_end - q))
  941.         {
  942.           if (end <= value || !strncasecomp ("true", value, end - value))
  943.             options->no_inline_p = FALSE;
  944.           else
  945.             options->no_inline_p = TRUE;
  946.         }
  947.  
  948.       q = end;
  949.       if (*q)
  950.         q++;
  951.     }
  952.  
  953.  
  954.   /* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0)
  955.      MIME parser.
  956.  
  957.      Basically, the problem is that the old part-numbering code was totally
  958.      busted: here's a comparison of the old and new numberings with a pair
  959.      of hypothetical messages (one with a single part, and one with nested
  960.      containers.)
  961.                                NEW:      OLD:  OR:
  962.          message/rfc822
  963.            image/jpeg           1         0     0
  964.  
  965.          message/rfc822
  966.            multipart/mixed      1         0     0
  967.              text/plain         1.1       1     1
  968.              image/jpeg         1.2       2     2
  969.              message/rfc822     1.3       -     3
  970.                text/plain       1.3.1     3     -
  971.              message/rfc822     1.4       -     4
  972.                multipart/mixed  1.4.1     4     -
  973.                  text/plain     1.4.1.1   4.1   -
  974.                  image/jpeg     1.4.1.2   4.2   -
  975.              text/plain         1.5       5     5
  976.  
  977.      The "NEW" column is how the current code counts.  The "OLD" column is
  978.      what "?part=" references would do in 3.0b4 and earlier; you'll see that
  979.      you couldn't directly refer to the child message/rfc822 objects at all!
  980.      But that's when it got really weird, because if you turned on
  981.      "Attachments As Links" (or used a URL like "?inline=false&part=...")
  982.      then you got a totally different numbering system (seen in the "OR"
  983.      column.)  Gag!
  984.  
  985.      So, the problem is, ClariNet had been using these part numbers in their
  986.      HTML news feeds, as a sleazy way of transmitting both complex HTML layouts
  987.      and images using NNTP as transport, without invoking HTTP.
  988.  
  989.      The following clause is to provide some small amount of backward
  990.      compatibility.  By looking at that table, one can see that in the new
  991.      model, "part=0" has no meaning, and neither does "part=2" or "part=3"
  992.      and so on.
  993.  
  994.      "part=1" is ambiguous between the old and new way, as is any part
  995.      specification that has a "." in it.
  996.  
  997.      So, the compatibility hack we do here is: if the part is "0", then map
  998.      that to "1".  And if the part is >= "2", then prepend "1." to it (so that
  999.      we map "2" to "1.2", and "3" to "1.3".)
  1000.  
  1001.      This leaves the URLs compatible in the cases of:
  1002.  
  1003.        = single part messages
  1004.        = references to elements of a top-level multipart except the first
  1005.  
  1006.      and leaves them incompatible for:
  1007.  
  1008.        = the first part of a top-level multipart
  1009.        = all elements deeper than the outermost part
  1010.  
  1011.      Life s#$%s when you don't properly think out things that end up turning
  1012.      into de-facto standards...
  1013.    */
  1014.   if (options->part_to_load &&
  1015.       !XP_STRCHR(options->part_to_load, '.'))        /* doesn't contain a dot */
  1016.     {
  1017.       if (!XP_STRCMP(options->part_to_load, "0"))        /* 0 */
  1018.         {
  1019.           XP_FREE(options->part_to_load);
  1020.           options->part_to_load = XP_STRDUP("1");
  1021.           if (!options->part_to_load)
  1022.             return MK_OUT_OF_MEMORY;
  1023.         }
  1024.       else if (XP_STRCMP(options->part_to_load, "1"))    /* not 1 */
  1025.         {
  1026.           const char *prefix = "1.";
  1027.           char *s = (char *) XP_ALLOC(XP_STRLEN(options->part_to_load) +
  1028.                                       XP_STRLEN(prefix) + 1);
  1029.           if (!s) return MK_OUT_OF_MEMORY;
  1030.           XP_STRCPY(s, prefix);
  1031.           XP_STRCAT(s, options->part_to_load);
  1032.           XP_FREE(options->part_to_load);
  1033.           options->part_to_load = s;
  1034.         }
  1035.     }
  1036.  
  1037.  
  1038.   return 0;
  1039. }
  1040.  
  1041.  
  1042. /* Some output-generation utility functions...
  1043.  */
  1044.  
  1045. int
  1046. MimeOptions_write(MimeDisplayOptions *opt, char *data, int32 length,
  1047.                   XP_Bool user_visible_p)
  1048. {
  1049.   int status = 0;
  1050.   void* closure = 0;
  1051.   if (!opt || !opt->output_fn || !opt->state)
  1052.     return 0;
  1053.  
  1054.   closure = opt->output_closure;
  1055.   if (!closure) closure = opt->stream_closure;
  1056.  
  1057.   XP_ASSERT(opt->state->first_data_written_p);
  1058.  
  1059.   if (opt->state->separator_queued_p && user_visible_p)
  1060.     {
  1061.       opt->state->separator_queued_p = FALSE;
  1062.       if (opt->state->separator_suppressed_p)
  1063.         opt->state->separator_suppressed_p = FALSE;
  1064.       else
  1065.         {
  1066.           char sep[] = "<HR WIDTH=\"90%\" SIZE=4>";
  1067.           int status = opt->output_fn(sep, XP_STRLEN(sep), closure);
  1068.           opt->state->separator_suppressed_p = FALSE;
  1069.           if (status < 0) return status;
  1070.         }
  1071.     }
  1072.   if (user_visible_p)
  1073.     opt->state->separator_suppressed_p = FALSE;
  1074.  
  1075.   if (length > 0)
  1076.     {
  1077.       status = opt->output_fn(data, length, closure);
  1078.       if (status < 0) return status;
  1079.     }
  1080.  
  1081.   return 0;
  1082. }
  1083.  
  1084. int
  1085. MimeObject_write(MimeObject *obj, char *output, int32 length,
  1086.                  XP_Bool user_visible_p)
  1087. {
  1088.   if (!obj->output_p) return 0;
  1089.  
  1090.   if (!obj->options->state->first_data_written_p)
  1091.     {
  1092.       int status = MimeObject_output_init(obj, 0);
  1093.       if (status < 0) return status;
  1094.       XP_ASSERT(obj->options->state->first_data_written_p);
  1095.     }
  1096.  
  1097.   return MimeOptions_write(obj->options, output, length, user_visible_p);
  1098. }
  1099.  
  1100. int
  1101. MimeObject_write_separator(MimeObject *obj)
  1102. {
  1103.   if (obj->options && obj->options->state)
  1104.     obj->options->state->separator_queued_p = TRUE;
  1105.   return 0;
  1106. }
  1107.  
  1108. int
  1109. MimeObject_output_init(MimeObject *obj, const char *content_type)
  1110. {
  1111.   if (obj &&
  1112.       obj->options &&
  1113.       obj->options->state &&
  1114.       !obj->options->state->first_data_written_p)
  1115.     {
  1116.       int status;
  1117.       const char *charset = 0;
  1118.       char *name = 0, *x_mac_type = 0, *x_mac_creator = 0;
  1119.  
  1120.       if (!obj->options->output_init_fn)
  1121.         {
  1122.           obj->options->state->first_data_written_p = TRUE;
  1123.           return 0;
  1124.         }
  1125.  
  1126.       if (obj->headers)
  1127.         {
  1128.           char *ct;
  1129.           name = MimeHeaders_get_name(obj->headers);
  1130.  
  1131.           ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE,
  1132.                                FALSE, FALSE);
  1133.           if (ct)
  1134.             {
  1135.               x_mac_type   = MimeHeaders_get_parameter(ct,PARAM_X_MAC_TYPE);
  1136.               x_mac_creator= MimeHeaders_get_parameter(ct,PARAM_X_MAC_CREATOR);
  1137.               FREEIF(obj->options->default_charset);
  1138.               obj->options->default_charset = MimeHeaders_get_parameter(ct, "charset");
  1139.               XP_FREE(ct);
  1140.             }
  1141.         }
  1142.  
  1143.       if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass))
  1144.         charset = ((MimeInlineText *)obj)->charset;
  1145.  
  1146.       if (!content_type)
  1147.         content_type = obj->content_type;
  1148.       if (!content_type)
  1149.         content_type = TEXT_PLAIN;
  1150.  
  1151.       status = obj->options->output_init_fn (content_type, charset, name,
  1152.                                              x_mac_type, x_mac_creator,
  1153.                                              obj->options->stream_closure);
  1154.       FREEIF(name);
  1155.       FREEIF(x_mac_type);
  1156.       FREEIF(x_mac_creator);
  1157.       obj->options->state->first_data_written_p = TRUE;
  1158.       return status;
  1159.     }
  1160.   return 0;
  1161. }
  1162.