Back to index

lightning-sunbird  0.9+nobinonly
mimemrel.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK *****
00038  * This Original Code has been modified by IBM Corporation. Modifications made by IBM 
00039  * described herein are Copyright (c) International Business Machines Corporation, 2000.
00040  * Modifications to Mozilla code or documentation identified per MPL Section 3.3
00041  *
00042  * Date             Modified by     Description of modification
00043  * 04/20/2000       IBM Corp.      OS/2 VisualAge build.
00044  */
00045 
00046 /* Thoughts on how to implement this:
00047 
00048    = if the type of this multipart/related is not text/html, then treat
00049      it the same as multipart/mixed.
00050 
00051    = For each part in this multipart/related
00052      = if this part is not the "top" part
00053        = then save this part to a tmp file or a memory object,
00054          kind-of like what we do for multipart/alternative sub-parts.
00055          If this is an object we're blocked on (see below) send its data along.
00056      = else
00057        = emit this part (remember, it's of type text/html)
00058        = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
00059          we intercept that.
00060          = if one of our cached parts has that cid, return the data for it.
00061          = else, "block", the same way the image library blocks layout when it
00062            doesn't yet have the size of the image.
00063        = at some point, layout may load a URL for <IMG SRC="relative/yyy">.
00064          we need to intercept that too.
00065          = expand the URL, and compare it to our cached objects.
00066            if it matches, return it.
00067          = else block on it.
00068 
00069    = once we get to the end, if we have any sub-part references that we're
00070      still blocked on, map over them:
00071      = if they're cid: references, close them ("broken image" results.)
00072      = if they're URLs, then load them in the normal way.
00073 
00074   --------------------------------------------------
00075 
00076   Ok, that's fairly complicated.  How about an approach where we go through
00077   all the parts first, and don't emit until the end?
00078 
00079    = if the type of this multipart/related is not text/html, then treat
00080      it the same as multipart/mixed.
00081 
00082    = For each part in this multipart/related
00083        = save this part to a tmp file or a memory object,
00084          like what we do for multipart/alternative sub-parts.
00085 
00086    = Emit the "top" part (the text/html one)
00087      = intercept all calls to NET_GetURL, to allow us to rewrite the URL.
00088        (hook into netlib, or only into imglib's calls to GetURL?)
00089        (make sure we're behaving in a context-local way.)
00090 
00091        = when a URL is loaded, look through our cached parts for a match.
00092          = if we find one, map that URL to a "cid:" URL
00093          = else, let it load normally
00094 
00095      = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
00096        it will do this either because that's what was in the HTML, or because
00097        that's how we "rewrote" the URLs when we intercepted NET_GetURL.
00098 
00099        = if one of our cached parts has the requested cid, return the data
00100          for it.
00101        = else, generate a "broken image"
00102 
00103    = free all the cached data
00104 
00105   --------------------------------------------------
00106 
00107   How hard would be an approach where we rewrite the HTML?
00108   (Looks like it's not much easier, and might be more error-prone.)
00109 
00110    = if the type of this multipart/related is not text/html, then treat
00111      it the same as multipart/mixed.
00112 
00113    = For each part in this multipart/related
00114        = save this part to a tmp file or a memory object,
00115          like what we do for multipart/alternative sub-parts.
00116 
00117    = Parse the "top" part, and emit slightly different HTML:
00118      = for each <IMG SRC>, <IMG LOWSRC>, <A HREF>?  Any others?
00119        = look through our cached parts for a matching URL
00120          = if we find one, map that URL to a "cid:" URL
00121          = else, let it load normally
00122 
00123      = at some point, layout may load a URL for <IMG SRC="cid:xxxx">.
00124        = if one of our cached parts has the requested cid, return the data
00125          for it.
00126        = else, generate a "broken image"
00127 
00128    = free all the cached data
00129  */
00130 #include "nsCOMPtr.h"
00131 #include "mimemrel.h"
00132 #include "mimemapl.h"
00133 #include "prmem.h"
00134 #include "prprf.h"
00135 #include "prlog.h"
00136 #include "plstr.h"
00137 #include "mimemoz2.h"
00138 #include "nsString.h"
00139 #include "nsIURL.h"
00140 #include "nsCRT.h"
00141 #include "msgCore.h"
00142 #include "nsMimeStringResources.h"
00143 #include "nsMimeTypes.h"
00144 #include "nsFileStream.h"
00145 #include "nsFileSpec.h"
00146 #include "mimebuf.h"
00147 #include "nsMsgUtils.h"
00148 
00149 //
00150 // External Defines...
00151 //
00152 extern nsFileSpec *nsMsgCreateTempFileSpec(const char *tFileName);
00153 
00154 #define MIME_SUPERCLASS mimeMultipartClass
00155 MimeDefClass(MimeMultipartRelated, MimeMultipartRelatedClass,
00156        mimeMultipartRelatedClass, &MIME_SUPERCLASS);
00157 
00158 
00159 class MimeHashValue
00160 {
00161 public:
00162   MimeHashValue(MimeObject *obj, char *url) {
00163     m_obj = obj;
00164     m_url = nsCRT::strdup(url);
00165   }
00166   virtual ~MimeHashValue() {
00167     if (m_url)
00168       PR_Free((void *)m_url);
00169   }
00170 
00171   MimeObject  *m_obj;
00172   char        *m_url;
00173 };
00174 
00175 static int
00176 MimeMultipartRelated_initialize(MimeObject* obj)
00177 {
00178   MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj;
00179   relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_BASE,
00180                      PR_FALSE, PR_FALSE);
00181   /* rhp: need this for supporting Content-Location */
00182   if (!relobj->base_url)
00183   {
00184     relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_LOCATION,
00185       PR_FALSE, PR_FALSE);
00186   }
00187   /* rhp: need this for supporting Content-Location */
00188 
00189   /* I used to have code here to test if the type was text/html.  Then I
00190      added multipart/alternative as being OK, too.  Then I found that the
00191      VCard spec seems to talk about having the first part of a
00192      multipart/related be an application/directory.  At that point, I decided
00193      to punt.  We handle anything as the first part, and stomp on the HTML it
00194      generates to adjust tags to point into the other parts.  This probably
00195      works out to something reasonable in most cases. */
00196 
00197   relobj->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues,
00198                                 (PLHashAllocOps *)NULL, NULL);
00199 
00200   if (!relobj->hash) return MIME_OUT_OF_MEMORY;
00201 
00202   relobj->input_file_stream = nsnull;
00203   relobj->output_file_stream = nsnull;
00204 
00205   return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj);
00206 }
00207 
00208 static PRIntn PR_CALLBACK 
00209 mime_multipart_related_nukehash(PLHashEntry *table, 
00210                                        PRIntn indx, void *arg)                             
00211 {
00212   if (table->key)
00213     PR_Free((char*) table->key);
00214 
00215   if (table->value)
00216     delete (MimeHashValue *)table->value;
00217 
00218   return HT_ENUMERATE_NEXT;   /* XP_Maphash will continue traversing the hash */
00219 }
00220 
00221 static void
00222 MimeMultipartRelated_finalize (MimeObject *obj)
00223 {
00224   MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj;
00225   PR_FREEIF(relobj->base_url);
00226   PR_FREEIF(relobj->curtag);
00227         PR_FREEIF(relobj->head_buffer);
00228         relobj->head_buffer_fp = 0;
00229         relobj->head_buffer_size = 0;
00230   if (relobj->hash) {
00231     PL_HashTableEnumerateEntries(relobj->hash, mime_multipart_related_nukehash, NULL);
00232     PL_HashTableDestroy(relobj->hash);
00233     relobj->hash = NULL;
00234   }
00235 
00236   if (relobj->input_file_stream) 
00237   {
00238     relobj->input_file_stream->close();
00239     delete relobj->input_file_stream;
00240     relobj->input_file_stream = nsnull;
00241   }
00242 
00243   if (relobj->output_file_stream) 
00244   {
00245     relobj->output_file_stream->close();
00246     delete relobj->output_file_stream;
00247     relobj->output_file_stream = nsnull;
00248   }
00249 
00250   if (relobj->file_buffer_spec) 
00251   {
00252     relobj->file_buffer_spec->Delete(PR_FALSE);
00253     delete relobj->file_buffer_spec;
00254     relobj->file_buffer_spec = nsnull;
00255   }
00256 
00257   ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj);
00258 }
00259 
00260 #define ISHEX(c) ( ((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F') )
00261 #define NONHEX(c) (!ISHEX(c))
00262 
00263 extern "C" char * 
00264 escape_unescaped_percents(const char *incomingURL)
00265 {
00266   const char *inC;
00267   char *outC;
00268   char *result = (char *) PR_Malloc(strlen(incomingURL)*3+1);
00269 
00270   if (result)
00271   {
00272     for(inC = incomingURL, outC = result; *inC != '\0'; inC++)
00273     {
00274       if (*inC == '%')
00275       {
00276         /* Check if either of the next two characters are non-hex. */
00277         if ( !*(inC+1) || NONHEX(*(inC+1)) || !*(inC+2) || NONHEX(*(inC+2)) )
00278         {
00279           /* Hex characters don't follow, escape the 
00280              percent char */
00281           *outC++ = '%'; *outC++ = '2'; *outC++ = '5';
00282         }
00283         else
00284         {
00285           /* Hex characters follow, so assume the percent 
00286              is escaping something else */
00287           *outC++ = *inC;
00288         }
00289       }
00290       else
00291         *outC++ = *inC;
00292     }
00293     *outC = '\0';
00294   }
00295 
00296   return result;
00297 }
00298 
00299 /* This routine is only necessary because the mailbox URL fed to us 
00300    by the winfe can contain spaces and '>'s in it. It's a hack. */
00301 static char *
00302 escape_for_mrel_subst(char *inURL)
00303 {
00304   char *output, *inC, *outC, *temp;
00305 
00306   /* nsCRT::strlen asserts the presence of a string in inURL */
00307   int size = strlen(inURL) + 1;
00308 
00309   for(inC = inURL; *inC; inC++)
00310     if ((*inC == ' ') || (*inC == '>')) 
00311       size += 2; /* space -> '%20', '>' -> '%3E', etc. */
00312 
00313   output = (char *)PR_MALLOC(size);
00314   if (output)
00315   {
00316     /* Walk through the source string, copying all chars
00317       except for spaces, which get escaped. */
00318     inC = inURL;
00319     outC = output;
00320     while(*inC)
00321     {
00322       if (*inC == ' ')
00323       {
00324         *outC++ = '%'; *outC++ = '2'; *outC++ = '0';
00325       }
00326       else if (*inC == '>')
00327       {
00328         *outC++ = '%'; *outC++ = '3'; *outC++ = 'E';
00329       }
00330       else
00331         *outC++ = *inC;
00332 
00333       inC++;
00334     }
00335     *outC = '\0';
00336   
00337     temp = escape_unescaped_percents(output);
00338     if (temp)
00339     {
00340       PR_FREEIF(output);
00341       output = temp;
00342     }
00343   }
00344   return output;
00345 }
00346 
00347 static PRBool
00348 MimeStartParamExists(MimeObject *obj, MimeObject* child)
00349 {
00350   char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
00351   char *st = (ct
00352               ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL)
00353               : 0);
00354   if (!st)
00355     return PR_FALSE;
00356 
00357   PR_FREEIF(st);
00358   PR_FREEIF(ct);
00359   return PR_TRUE;
00360 }
00361 
00362 static PRBool
00363 MimeThisIsStartPart(MimeObject *obj, MimeObject* child)
00364 {
00365   PRBool rval = PR_FALSE;
00366   char *ct, *st, *cst;
00367 
00368   ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE);
00369   st = (ct
00370         ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL)
00371         : 0);
00372   if (!st)
00373     return PR_FALSE;
00374 
00375   cst = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, PR_FALSE, PR_FALSE);
00376   if (!cst)
00377     rval = PR_FALSE;
00378   else
00379   {
00380     char *tmp = cst;
00381     if (*tmp == '<') 
00382     {
00383       int length;
00384       tmp++;
00385       length = strlen(tmp);
00386       if (length > 0 && tmp[length - 1] == '>') 
00387       {
00388         tmp[length - 1] = '\0';
00389       }
00390     }
00391 
00392     rval = (!nsCRT::strcmp(st, tmp));
00393   }
00394 
00395   PR_FREEIF(st);
00396   PR_FREEIF(ct);
00397   PR_FREEIF(cst);
00398   return rval;
00399 }
00400 /* rhp - gotta support the "start" parameter */
00401 
00402 char *
00403 MakeAbsoluteURL(char *base_url, char *relative_url)
00404 {
00405   char            *retString = nsnull;
00406   nsIURI          *base = nsnull;
00407 
00408   // if either is NULL, just return the relative if safe...
00409   if (!base_url || !relative_url)
00410   {
00411     if (!relative_url)
00412       return nsnull;
00413 
00414     NS_MsgSACopy(&retString, relative_url);
00415     return retString;
00416   }
00417 
00418   nsresult err = nsMimeNewURI(&base, base_url, nsnull);
00419   if (err != NS_OK) 
00420     return nsnull;
00421 
00422   nsCAutoString spec;
00423 
00424   nsIURI    *url = nsnull;
00425   err = nsMimeNewURI(&url, relative_url, base);
00426   if (err != NS_OK) 
00427     goto done;
00428   
00429   err = url->GetSpec(spec);
00430   if (err)
00431   {
00432     retString = nsnull;
00433     goto done;
00434   }
00435   retString = ToNewCString(spec);
00436   
00437 done:
00438   NS_IF_RELEASE(url);
00439   NS_IF_RELEASE(base);
00440   return retString;
00441 }
00442 
00443 static PRBool
00444 MimeMultipartRelated_output_child_p(MimeObject *obj, MimeObject* child)
00445 {
00446   MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
00447 
00448   /* rhp - Changed from "if (relobj->head_loaded)" alone to support the 
00449            start parameter
00450    */
00451   if (
00452        (relobj->head_loaded) || 
00453        (MimeStartParamExists(obj, child) && !MimeThisIsStartPart(obj, child)) 
00454      )
00455   {
00456     /* This is a child part.  Just remember the mapping between the URL
00457        it represents and the part-URL to get it back. */
00458 
00459     char* location = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION,
00460                      PR_FALSE, PR_FALSE);
00461     if (!location) {
00462       char* tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID,
00463                     PR_FALSE, PR_FALSE);
00464       if (tmp) {
00465         char* tmp2 = tmp;
00466         if (*tmp2 == '<') {
00467           int length;
00468           tmp2++;
00469           length = strlen(tmp2);
00470           if (length > 0 && tmp2[length - 1] == '>') {
00471             tmp2[length - 1] = '\0';
00472           }
00473         }
00474         location = PR_smprintf("cid:%s", tmp2);
00475         PR_Free(tmp);
00476       }
00477     }
00478 
00479     if (location) {
00480       char *absolute;
00481       char *base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
00482                        PR_FALSE, PR_FALSE);
00483       absolute = MakeAbsoluteURL(base_url ? base_url : relobj->base_url, location);
00484 
00485       PR_FREEIF(base_url);
00486       PR_Free(location);
00487       if (absolute) {
00488         nsCAutoString partnum;
00489         nsCAutoString imappartnum;
00490         partnum.Adopt(mime_part_address(child));
00491         if (!partnum.IsEmpty()) {
00492           if (obj->options->missing_parts)
00493           {
00494             char * imappart = mime_imap_part_address(child);
00495             if (imappart)
00496               imappartnum.Adopt(imappart);
00497           }
00498 
00499           /*
00500             AppleDouble part need special care: we need to output only the data fork part of it.
00501             The problem at this point is that we haven't yet decoded the children of the AppleDouble
00502             part therfore we will have to hope the datafork is the second one!
00503           */
00504           if (mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
00505             partnum.Append(".2");
00506 
00507           char* part;
00508           if (!imappartnum.IsEmpty()) 
00509             part = mime_set_url_imap_part(obj->options->url, imappartnum.get(), partnum.get());
00510           else
00511           {
00512             char *no_part_url = nsnull;
00513             if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
00514               no_part_url = mime_get_base_url(obj->options->url);
00515             if (no_part_url)
00516             {
00517               part = mime_set_url_part(no_part_url, partnum.get(), PR_FALSE);
00518               PR_Free(no_part_url);
00519             }
00520             else
00521               part = mime_set_url_part(obj->options->url, partnum.get(), PR_FALSE);
00522           }
00523           if (part)
00524           {
00525             char *name = MimeHeaders_get_name(child->headers, child->options);
00526             // let's stick the filename in the part so save as will work.
00527             if (name)
00528             {
00529               char *savePart = part;
00530               part = PR_smprintf("%s&filename=%s", savePart, name);
00531               PR_Free(savePart);
00532               PR_Free(name);
00533             }
00534             char *temp = part;
00535             /* If there's a space in the url, escape the url.
00536                (This happens primarily on Windows and Unix.) */
00537             if (PL_strchr(part, ' ') || PL_strchr(part, '>') || PL_strchr(part, '%'))
00538               temp = escape_for_mrel_subst(part);
00539               MimeHashValue * value = new MimeHashValue(child, temp);
00540               PL_HashTableAdd(relobj->hash, absolute, value);
00541 
00542             /* rhp - If this part ALSO has a Content-ID we need to put that into
00543                      the hash table and this is what this code does
00544              */
00545             {
00546               char *tloc;
00547               char *tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, PR_FALSE, PR_FALSE);
00548               if (tmp) 
00549               {
00550                 char* tmp2 = tmp;
00551                 if (*tmp2 == '<') 
00552                 {
00553                   int length;
00554                   tmp2++;
00555                   length = strlen(tmp2);
00556                   if (length > 0 && tmp2[length - 1] == '>') 
00557                   {
00558                     tmp2[length - 1] = '\0';
00559                   }
00560                 }
00561                 
00562                 tloc = PR_smprintf("cid:%s", tmp2);
00563                 PR_Free(tmp);
00564                 if (tloc)
00565                 {
00566                     MimeHashValue * value = new MimeHashValue(child, temp);
00567                     PL_HashTableAdd(relobj->hash, tloc, value);
00568                 }
00569               }
00570             }
00571             /*  rhp - End of putting more stuff into the hash table */
00572 
00573               /* it's possible that temp pointer is the same than the part pointer,
00574                  therefore be carefull to not freeing twice the same pointer */
00575               if (temp && temp != part)
00576                 PR_Free(temp);
00577               PR_Free(part);
00578             }
00579           }
00580         }
00581       }
00582   } else {
00583     /* Ah-hah!  We're the head object.  */
00584     char* base_url;
00585     relobj->head_loaded = PR_TRUE;
00586     relobj->headobj = child;
00587     relobj->buffered_hdrs = MimeHeaders_copy(child->headers);
00588     base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE,
00589                    PR_FALSE, PR_FALSE);
00590     /* rhp: need this for supporting Content-Location */
00591     if (!base_url)
00592     {
00593       base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, PR_FALSE, PR_FALSE);
00594     }
00595     /* rhp: need this for supporting Content-Location */
00596 
00597     if (base_url) {
00598       /* If the head object has a base_url associated with it, use
00599          that instead of any base_url that may have been associated
00600          with the multipart/related. */
00601       PR_FREEIF(relobj->base_url);
00602       relobj->base_url = base_url;
00603     }
00604   }
00605   if (obj->options && !obj->options->write_html_p
00606 #ifdef MIME_DRAFTS
00607     && !obj->options->decompose_file_p
00608 #endif /* MIME_DRAFTS */
00609     )
00610     {
00611     return PR_TRUE;
00612     }
00613 
00614   return PR_FALSE;      /* Don't actually parse this child; we'll handle
00615                  all that at eof time. */
00616 }
00617 
00618 static int
00619 MimeMultipartRelated_parse_child_line (MimeObject *obj,
00620                      char *line, PRInt32 length,
00621                      PRBool first_line_p)
00622 {
00623   MimeContainer *cont = (MimeContainer *) obj;
00624   MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
00625   int status;
00626   MimeObject *kid;
00627 
00628   if (obj->options && !obj->options->write_html_p
00629 #ifdef MIME_DRAFTS
00630     && !obj->options->decompose_file_p
00631 #endif /* MIME_DRAFTS */
00632     )
00633     {
00634     /* Oh, just go do the normal thing... */
00635     return ((MimeMultipartClass*)&MIME_SUPERCLASS)->
00636       parse_child_line(obj, line, length, first_line_p);
00637     }
00638 
00639   /* Throw it away if this isn't the head object.  (Someday, maybe we'll
00640      cache it instead.) */
00641   PR_ASSERT(cont->nchildren > 0);
00642   if (cont->nchildren <= 0)
00643     return -1;
00644   kid = cont->children[cont->nchildren-1];
00645   PR_ASSERT(kid);
00646   if (!kid) return -1;
00647   if (kid != relobj->headobj) return 0;
00648 
00649   /* Buffer this up (###tw much code duplication from mimemalt.c) */
00650   /* If we don't yet have a buffer (either memory or file) try and make a
00651      memory buffer. */
00652   if (!relobj->head_buffer && !relobj->file_buffer_spec) {
00653     int target_size = 1024 * 50;       /* try for 50k */
00654     while (target_size > 0) {
00655       relobj->head_buffer = (char *) PR_MALLOC(target_size);
00656       if (relobj->head_buffer) break;  /* got it! */
00657       target_size -= (1024 * 5);     /* decrease it and try again */
00658     }
00659 
00660     if (relobj->head_buffer) {
00661       relobj->head_buffer_size = target_size;
00662     } else {
00663       relobj->head_buffer_size = 0;
00664     }
00665 
00666     relobj->head_buffer_fp = 0;
00667   }
00668 
00669   /* Ok, if at this point we still don't have either kind of buffer, try and
00670      make a file buffer. */
00671   if (!relobj->head_buffer && !relobj->file_buffer_spec) 
00672   {
00673     relobj->file_buffer_spec = nsMsgCreateTempFileSpec("nsma");
00674     if (!relobj->file_buffer_spec) 
00675       return MIME_OUT_OF_MEMORY;
00676 
00677     relobj->output_file_stream = new nsOutputFileStream(*(relobj->file_buffer_spec), PR_WRONLY | PR_CREATE_FILE, 00600);
00678     if (!relobj->output_file_stream) 
00679     {
00680       return MIME_UNABLE_TO_OPEN_TMP_FILE;
00681     }
00682   }
00683   
00684   PR_ASSERT(relobj->head_buffer || relobj->output_file_stream);
00685 
00686 
00687   /* If this line will fit in the memory buffer, put it there.
00688    */
00689   if (relobj->head_buffer &&
00690       relobj->head_buffer_fp + length < relobj->head_buffer_size) {
00691     memcpy(relobj->head_buffer + relobj->head_buffer_fp, line, length);
00692     relobj->head_buffer_fp += length;
00693   } else {
00694     /* Otherwise it won't fit; write it to the file instead. */
00695 
00696     /* If the file isn't open yet, open it, and dump the memory buffer
00697        to it. */
00698     if (!relobj->output_file_stream) 
00699     {
00700       if (!relobj->file_buffer_spec) 
00701       {
00702         relobj->file_buffer_spec = nsMsgCreateTempFileSpec("nsma");
00703       }
00704 
00705       if (!relobj->file_buffer_spec) 
00706         return MIME_OUT_OF_MEMORY;
00707 
00708       relobj->output_file_stream = new nsOutputFileStream(*(relobj->file_buffer_spec), PR_WRONLY | PR_CREATE_FILE, 00600);
00709       if (!relobj->output_file_stream) 
00710       {
00711         return MIME_UNABLE_TO_OPEN_TMP_FILE;
00712       }
00713 
00714       if (relobj->head_buffer && relobj->head_buffer_fp) 
00715       {
00716         status = relobj->output_file_stream->write(relobj->head_buffer,
00717                                                    relobj->head_buffer_fp);
00718         if (status < relobj->head_buffer_fp)
00719           return MIME_UNABLE_TO_OPEN_TMP_FILE;
00720       }
00721 
00722       PR_FREEIF(relobj->head_buffer);
00723       relobj->head_buffer_fp = 0;
00724       relobj->head_buffer_size = 0;
00725     }
00726 
00727     /* Dump this line to the file. */
00728     status = relobj->output_file_stream->write(line, length);
00729     if (status < length) 
00730       return status;
00731   }
00732 
00733   return 0;
00734 }
00735 
00736 
00737 
00738 
00739 static int
00740 real_write(MimeMultipartRelated* relobj, const char* buf, PRInt32 size)
00741 {
00742   MimeObject* obj = (MimeObject*) relobj;
00743   void* closure = relobj->real_output_closure;
00744 
00745 #ifdef MIME_DRAFTS
00746   if ( obj->options &&
00747      obj->options->decompose_file_p &&
00748      obj->options->decompose_file_output_fn )
00749   {
00750     return obj->options->decompose_file_output_fn 
00751     (buf, size, obj->options->stream_closure);
00752   }
00753   else
00754 #endif /* MIME_DRAFTS */
00755   {
00756     if (!closure) {
00757       MimeObject* lobj = (MimeObject*) relobj;
00758       closure = lobj->options->stream_closure;
00759     }
00760     return relobj->real_output_fn(buf, size, closure);
00761   }
00762 }
00763 
00764 
00765 static int
00766 push_tag(MimeMultipartRelated* relobj, const char* buf, PRInt32 size)
00767 {
00768   if (size + relobj->curtag_length > relobj->curtag_max) {
00769     relobj->curtag_max += 2 * size;
00770     if (relobj->curtag_max < 1024) relobj->curtag_max = 1024;
00771     if (!relobj->curtag) {
00772       relobj->curtag = (char*) PR_MALLOC(relobj->curtag_max);
00773     } else {
00774       relobj->curtag = (char*) PR_Realloc(relobj->curtag,
00775                         relobj->curtag_max);
00776     }
00777     if (!relobj->curtag) return MIME_OUT_OF_MEMORY;
00778   }
00779   memcpy(relobj->curtag + relobj->curtag_length, buf, size);
00780   relobj->curtag_length += size;
00781   return 0;
00782 }
00783 
00784 static PRBool accept_related_part(MimeMultipartRelated* relobj, MimeObject* part_obj)
00785 {
00786   if (!relobj || !part_obj)
00787     return PR_FALSE;
00788     
00789   /* before accepting it as a valid related part, make sure we
00790      are able to display it inline as an embedded object. Else just ignore
00791      it, that will prevent any bad surprise... */
00792   MimeObjectClass *clazz = mime_find_class (part_obj->content_type, part_obj->headers, part_obj->options, PR_FALSE);
00793   if (clazz ? clazz->displayable_inline_p(clazz, part_obj->headers) : PR_FALSE)
00794     return PR_TRUE;
00795 
00796   /* ...but we always accept it if it's referenced by an anchor */
00797   return (relobj->curtag && relobj->curtag_length >= 3 &&
00798     (relobj->curtag[1] == 'A' || relobj->curtag[1] == 'a') && nsCRT::IsAsciiSpace(relobj->curtag[2]));
00799 }
00800 
00801 static int
00802 flush_tag(MimeMultipartRelated* relobj)
00803 {
00804   int length = relobj->curtag_length;
00805   char* buf;
00806   int status;
00807   
00808   if (relobj->curtag == NULL || length == 0) return 0;
00809 
00810   status = push_tag(relobj, "", 1); /* Push on a trailing NULL. */
00811   if (status < 0) return status;
00812   buf = relobj->curtag;
00813   PR_ASSERT(*buf == '<' && buf[length - 1] == '>');
00814   while (*buf) {
00815     char c;
00816     char* absolute;
00817     char* part_url;
00818     char* ptr = buf;
00819     char *ptr2;
00820     char quoteDelimiter = '\0';
00821     while (*ptr && *ptr != '=') ptr++;
00822     if (*ptr == '=') {
00823       ptr++;
00824       if (*ptr == '"' || *ptr == '\'') {
00825         quoteDelimiter = *ptr;
00826         /* Take up the quote and leading space here as well. */
00827         /* Safe because there's a '>' at the end */
00828         do {ptr++;} while (nsCRT::IsAsciiSpace(*ptr));
00829       }
00830     }
00831     status = real_write(relobj, buf, ptr - buf);
00832     if (status < 0) return status;
00833     buf = ptr;
00834     if (!*buf) break;
00835     if (quoteDelimiter) 
00836     {
00837       ptr = PL_strnchr(buf, quoteDelimiter, length - (buf - relobj->curtag));
00838     } else {
00839       for (ptr = buf; *ptr ; ptr++) {
00840         if (*ptr == '>' || nsCRT::IsAsciiSpace(*ptr)) break;
00841       }
00842       PR_ASSERT(*ptr);
00843     }
00844     if (!ptr || !*ptr) break;
00845 
00846     while(buf < ptr)
00847     {
00848       /* ### mwelch For each word in the value string, see if
00849                       the word is a cid: URL. If so, attempt to
00850               substitute the appropriate mailbox part URL in
00851               its place. */
00852       ptr2=buf; /* walk from the left end rightward */
00853       while((ptr2<ptr) && (!nsCRT::IsAsciiSpace(*ptr2)))
00854         ptr2++;
00855       /* Compare the beginning of the word with "cid:". Yuck. */
00856       if (((ptr2 - buf) > 4) && 
00857         ((buf[0]=='c' || buf[0]=='C') && 
00858          (buf[1]=='i' || buf[1]=='I') && 
00859          (buf[2]=='d' || buf[2]=='D') && 
00860           buf[3]==':'))
00861       {
00862         // Make sure it's lowercase, otherwise it won't be found in the hash table
00863         buf[0] = 'c'; buf[1] = 'i'; buf[2] = 'd';
00864 
00865         /* Null terminate the word so we can... */
00866         c = *ptr2;
00867         *ptr2 = '\0';
00868         
00869         /* Construct a URL out of the word. */
00870         absolute = MakeAbsoluteURL(relobj->base_url, buf);
00871 
00872         /* See if we have a mailbox part URL
00873            corresponding to this cid. */
00874         part_url = nsnull;
00875         MimeHashValue * value = nsnull;
00876         if (absolute)
00877         {
00878           value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf);
00879           part_url = value ? value->m_url : nsnull;
00880           PR_FREEIF(absolute);
00881         }
00882         
00883         /*If we found a mailbox part URL, write that out instead.*/
00884         if (part_url && accept_related_part(relobj, value->m_obj))
00885         {
00886           status = real_write(relobj, part_url, strlen(part_url));
00887           if (status < 0) return status;
00888           buf = ptr2; /* skip over the cid: URL we substituted */
00889 
00890           /* don't show that object as attachment */
00891           if (value->m_obj)
00892             value->m_obj->dontShowAsAttachment = PR_TRUE;
00893         }
00894         
00895         /* Restore the character that we nulled. */
00896         *ptr2 = c;
00897       }
00898       /* rhp - if we get here, we should still check against the hash table! */
00899       else 
00900       {
00901         char holder = *ptr2;
00902         char *realout;
00903 
00904         *ptr2 = '\0';
00905     
00906         /* Construct a URL out of the word. */
00907         absolute = MakeAbsoluteURL(relobj->base_url, buf);
00908 
00909         /* See if we have a mailbox part URL
00910            corresponding to this cid. */
00911         MimeHashValue * value;
00912         if (absolute)
00913           value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, absolute);
00914         else
00915           value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf);
00916         realout = value ? value->m_url : nsnull;
00917 
00918         *ptr2 = holder;
00919         PR_FREEIF(absolute);
00920 
00921         if (realout && accept_related_part(relobj, value->m_obj))
00922         {
00923           status = real_write(relobj, realout, strlen(realout));
00924           if (status < 0) return status;
00925           buf = ptr2; /* skip over the cid: URL we substituted */
00926 
00927           /* don't show that object as attachment */
00928           if (value->m_obj)
00929             value->m_obj->dontShowAsAttachment = PR_TRUE;
00930         }
00931       }
00932       /* rhp - if we get here, we should still check against the hash table! */
00933 
00934       /* Advance to the beginning of the next word, or to
00935          the end of the value string. */
00936       while((ptr2<ptr) && (nsCRT::IsAsciiSpace(*ptr2)))
00937         ptr2++;
00938 
00939       /* Write whatever original text remains after
00940          cid: URL substitution. */
00941       status = real_write(relobj, buf, ptr2-buf);
00942       if (status < 0) return status;
00943       buf = ptr2;
00944     }
00945   }
00946   if (buf && *buf) {
00947     status = real_write(relobj, buf, strlen(buf));
00948     if (status < 0) return status;
00949   }
00950   relobj->curtag_length = 0;
00951   return 0;
00952 }
00953 
00954 
00955 
00956 static int
00957 mime_multipart_related_output_fn(const char* buf, PRInt32 size, void *stream_closure)
00958 {
00959   MimeMultipartRelated *relobj = (MimeMultipartRelated *) stream_closure;
00960   char* ptr;
00961   PRInt32 delta;
00962   int status;
00963   while (size > 0) {
00964     if (relobj->curtag_length > 0) {
00965       ptr = PL_strnchr(buf, '>', size);
00966       if (!ptr) {
00967         return push_tag(relobj, buf, size);
00968       }
00969       delta = ptr - buf + 1;
00970       status = push_tag(relobj, buf, delta);
00971       if (status < 0) return status;
00972       status = flush_tag(relobj);
00973       if (status < 0) return status;
00974       buf += delta;
00975       size -= delta;
00976     }
00977     ptr = PL_strnchr(buf, '<', size);
00978     if (ptr && ptr - buf >= size) ptr = 0;
00979     if (!ptr) {
00980       return real_write(relobj, buf, size);
00981     }
00982     delta = ptr - buf;
00983     status = real_write(relobj, buf, delta);
00984     if (status < 0) return status;
00985     buf += delta;
00986     size -= delta;
00987     PR_ASSERT(relobj->curtag_length == 0);
00988     status = push_tag(relobj, buf, 1);
00989     if (status < 0) return status;
00990     PR_ASSERT(relobj->curtag_length == 1);
00991     buf++;
00992     size--;
00993   }
00994   return 0;
00995 }
00996 
00997 
00998 static int
00999 MimeMultipartRelated_parse_eof (MimeObject *obj, PRBool abort_p)
01000 {
01001   /* OK, all the necessary data has been collected.  We now have to spew out
01002      the HTML.  We let it go through all the normal mechanisms (which
01003      includes content-encoding handling), and intercept the output data to do
01004      translation of the tags.  Whee. */
01005   MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj;
01006   int status = 0;
01007   MimeObject *body;
01008   char* ct;
01009   const char* dct;
01010 
01011   status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
01012   if (status < 0) goto FAIL;
01013 
01014   if (!relobj->headobj) return 0;
01015 
01016   ct = (relobj->buffered_hdrs
01017       ? MimeHeaders_get (relobj->buffered_hdrs, HEADER_CONTENT_TYPE,
01018                PR_TRUE, PR_FALSE)
01019       : 0);
01020   dct = (((MimeMultipartClass *) obj->clazz)->default_part_type);
01021 
01022   relobj->real_output_fn = obj->options->output_fn;
01023   relobj->real_output_closure = obj->options->output_closure;
01024 
01025   obj->options->output_fn = mime_multipart_related_output_fn;
01026   obj->options->output_closure = obj;
01027 
01028   body = mime_create(((ct && *ct) ? ct : (dct ? dct : TEXT_HTML)),
01029              relobj->buffered_hdrs, obj->options);
01030   if (!body) {
01031     status = MIME_OUT_OF_MEMORY;
01032     goto FAIL;
01033   }
01034   status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body);
01035   if (status < 0) {
01036     mime_free(body);
01037     goto FAIL;
01038   }
01039 
01040   body->dontShowAsAttachment = body->clazz->displayable_inline_p(body->clazz, body->headers);
01041 
01042 #ifdef MIME_DRAFTS
01043   if ( obj->options && 
01044      obj->options->decompose_file_p &&
01045      obj->options->decompose_file_init_fn &&
01046      (relobj->file_buffer_spec || relobj->head_buffer))
01047   {
01048     status = obj->options->decompose_file_init_fn ( obj->options->stream_closure,
01049                             relobj->buffered_hdrs );
01050     if (status < 0) return status;
01051   }
01052 #endif /* MIME_DRAFTS */
01053 
01054 
01055   /* Now that we've added this new object to our list of children,
01056      start its parser going. */
01057   status = body->clazz->parse_begin(body);
01058   if (status < 0) goto FAIL;
01059   
01060   if (relobj->head_buffer) 
01061   {
01062     /* Read it out of memory. */
01063     PR_ASSERT(!relobj->file_buffer_spec && !relobj->input_file_stream);
01064 
01065     status = body->clazz->parse_buffer(relobj->head_buffer,
01066                          relobj->head_buffer_fp,
01067                          body);
01068   } 
01069   else if (relobj->file_buffer_spec) 
01070   {
01071     /* Read it off disk. */
01072     char *buf;
01073     PRInt32 buf_size = 10 * 1024;  /* 10k; tune this? */
01074 
01075     PR_ASSERT(relobj->head_buffer_size == 0 &&
01076           relobj->head_buffer_fp == 0);
01077     PR_ASSERT(relobj->file_buffer_spec);
01078     if (!relobj->file_buffer_spec) 
01079     {
01080       status = -1;
01081       goto FAIL;
01082     }
01083 
01084     buf = (char *) PR_MALLOC(buf_size);
01085     if (!buf) 
01086     {
01087       status = MIME_OUT_OF_MEMORY;
01088       goto FAIL;
01089     }
01090 
01091     // First, close the output file to open the input file!
01092     if (relobj->output_file_stream)
01093       relobj->output_file_stream->close();
01094 
01095     relobj->input_file_stream = new nsInputFileStream(*(relobj->file_buffer_spec));
01096     if (!relobj->input_file_stream) 
01097     {
01098       PR_Free(buf);
01099       status = MIME_UNABLE_TO_OPEN_TMP_FILE;
01100       goto FAIL;
01101     }
01102 
01103     while(1) 
01104     {
01105       PRInt32 rstatus = relobj->input_file_stream->read(buf, buf_size - 1);
01106       if (rstatus <= 0) 
01107       {
01108         status = rstatus;
01109         break;
01110       } 
01111       else 
01112       {
01113         /* It would be really nice to be able to yield here, and let
01114            some user events and other input sources get processed.
01115            Oh well. */
01116 
01117         status = body->clazz->parse_buffer(buf, rstatus, body);
01118         if (status < 0) break;
01119       }
01120     }
01121     PR_Free(buf);
01122   }
01123 
01124   if (status < 0) goto FAIL;
01125 
01126   /* Done parsing. */
01127   status = body->clazz->parse_eof(body, PR_FALSE);
01128   if (status < 0) goto FAIL;
01129   status = body->clazz->parse_end(body, PR_FALSE);
01130   if (status < 0) goto FAIL;
01131 
01132 FAIL:
01133 
01134 #ifdef MIME_DRAFTS
01135   if ( obj->options &&
01136      obj->options->decompose_file_p &&
01137      obj->options->decompose_file_close_fn &&
01138      (relobj->file_buffer_spec || relobj->head_buffer)) {
01139   status = obj->options->decompose_file_close_fn ( obj->options->stream_closure );
01140   if (status < 0) return status;
01141   }
01142 #endif /* MIME_DRAFTS */
01143 
01144   relobj->headobj = NULL;
01145   obj->options->output_fn = relobj->real_output_fn;
01146   obj->options->output_closure = relobj->real_output_closure;
01147 
01148   return status;
01149 } 
01150 
01151 
01152 
01153 
01154 static int
01155 MimeMultipartRelatedClassInitialize(MimeMultipartRelatedClass *clazz)
01156 {
01157   MimeObjectClass    *oclass = (MimeObjectClass *) clazz;
01158   MimeMultipartClass *mclass = (MimeMultipartClass *) clazz;
01159   PR_ASSERT(!oclass->class_initialized);
01160   oclass->initialize       = MimeMultipartRelated_initialize;
01161   oclass->finalize         = MimeMultipartRelated_finalize;
01162   oclass->parse_eof        = MimeMultipartRelated_parse_eof;
01163   mclass->output_child_p   = MimeMultipartRelated_output_child_p;
01164   mclass->parse_child_line = MimeMultipartRelated_parse_child_line;
01165   return 0;
01166 }