home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libnet / mkstream.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  38.1 KB  |  1,244 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.  *  Do stream related stuff like push data down the
  20.  *  stream and determine which converter to use to
  21.  *  set up the stream.
  22.  * Additions/Changes by Judson Valeski 1998
  23.  */
  24. #include "mkutils.h"
  25. #include "mkstream.h"
  26. #include "mkgeturl.h"
  27. #include "mktcp.h"
  28.  
  29. #include "cvview.h"
  30.  
  31. #ifdef XP_UNIX
  32. #include "cvextcon.h"
  33. #endif
  34.  
  35. /* for XP_GetString() */
  36. #include "xpgetstr.h"
  37. extern int XP_ALERT_CANTFIND_CONVERTER;
  38.  
  39.  
  40. #ifdef PROFILE
  41. #pragma profile on
  42. #endif
  43.  
  44. typedef struct _net_ConverterStruct {
  45.      XP_List       * converter_stack;  /* the ordered list of converters;
  46.                                         * elements are net_ConverterElement's
  47.                                         */
  48.      char          * format_in;        /* the input (mime) type that the 
  49.                                         * function accepts 
  50.                                         */
  51.      char          * encoding_in;      /* the input content-encoding that the
  52.                                         * function accepts, or 0 for `any'.
  53.                                         */
  54.      FO_Present_Types format_out;      /* the output type of the function */
  55.      Bool            bAutomated;            /* this flag currently only used by Window on the
  56.                                             client side. */
  57. } net_ConverterStruct;
  58.  
  59. typedef struct _net_ConverterElement {
  60.      NET_Converter * converter;        /* the converter function */
  61.      void          * data_obj;         /* a converter specific data object 
  62.                                         * passed in at the
  63.                                         * time of registration 
  64.                                         */
  65. } net_ConverterElement;
  66.  
  67. /* MWH - this is an ugly implement to get  get around the following: I need to pass
  68.     a flag to NET_RegisterContentTypeConverter(), but I don't want to change the API.
  69.     If we changed this API please fixed this problem.
  70. */
  71.  
  72. static Bool autoFlag = 0;  
  73.  
  74. #define MAX_FORMATS_OUT FO_ONLY_FROM_CACHE_AND_LOAD_HTML_HELP_MAP_FILE +1
  75.  
  76. static Bool needInit = TRUE;  /* this is the flag to tell us that we need to initialize
  77.                                 net_converter_list, and  net_decoder_list. */
  78.  
  79. PRIVATE XP_List * net_converter_list[MAX_FORMATS_OUT];  /* a list of converters */
  80. PRIVATE XP_List * net_decoder_list[MAX_FORMATS_OUT];    /* a list of decoders */
  81.  
  82. #define NET_MIME_EXACT_MATCH    1
  83. #define NET_MIME_PARTIAL_MATCH  2
  84. #define NET_WILDCARD_MATCH      3
  85.  
  86. /* will compare two mime times and return
  87.  * NET_MIME_EXACT_MATCH if they match exactly
  88.  * or NET_MIME_PARTIAL_MATCH if they match due
  89.  * to a wildcard entry (image.*, or movie.*).
  90.  *
  91.  * if 'partial' equals '*' then
  92.  * NET_WILDCARD_MATCH is returned
  93.  * 
  94.  * If there is no match then 0(zero) is returned
  95.  */
  96. PRIVATE int
  97. net_compare_mime_types(char * absolute, char * partial)
  98. {
  99.    char *star;
  100.  
  101.    TRACEMSG(("StreamBuilder: Comparing %s and %s\n",absolute, partial));
  102.  
  103.    if(!strcasecomp(absolute, partial))
  104.       return(NET_MIME_EXACT_MATCH);
  105.  
  106.    if(!XP_STRCMP(partial, "*"))
  107.       return(NET_WILDCARD_MATCH);
  108.  
  109.    if((star = XP_STRCHR(partial, '*')) == 0)
  110.       return(0);  /* not a wildcard mime type */
  111.  
  112.    /* compare just the part before the slash star
  113.     *
  114.     */
  115.    if(!strncasecomp(absolute, partial, (star-1)-partial))
  116.       return(NET_MIME_PARTIAL_MATCH);
  117.  
  118.    return(0); /* no match */
  119. }
  120.  
  121. PUBLIC XP_Bool
  122. NET_HaveConverterForMimeType(char *content_type)
  123. {
  124.     net_ConverterStruct * cs_ptr;
  125.     XP_List * ct_list_ptr = net_converter_list[FO_PRESENT];
  126.  
  127.     while ((cs_ptr=(net_ConverterStruct *) XP_ListNextObject(ct_list_ptr))
  128.            != 0)
  129.     {
  130.       if (FO_PRESENT == cs_ptr->format_out)
  131.       {
  132.         int compare_val = net_compare_mime_types (content_type,    cs_ptr->format_in);
  133.         if (compare_val == NET_MIME_PARTIAL_MATCH || compare_val == NET_MIME_EXACT_MATCH)
  134.           return TRUE;
  135.       }
  136.     }
  137.  
  138.     return FALSE;
  139. }
  140.  
  141. /* Find a converter routine to create a stream and return the stream struct
  142. */
  143. PUBLIC NET_StreamClass * 
  144. NET_StreamBuilder  (FO_Present_Types format_out,
  145.                     URL_Struct  *URL_s,
  146.                     MWContext   *context)
  147. {
  148.     net_ConverterStruct * cs_ptr;
  149.     XP_List * ct_list_ptr = net_converter_list[format_out];
  150.  
  151.  
  152.  
  153.     XP_List * ce_list_ptr = net_decoder_list[format_out];
  154.  
  155.     assert (URL_s->content_type);
  156.     
  157.     TRACEMSG(("Entering StreamBuilder:\n\
  158.      F-in: %s\n\
  159.     F-out: %d\n\
  160.       Enc: %s\n",URL_s->content_type,format_out, 
  161.     (URL_s->content_encoding ? URL_s->content_encoding : "None Specified")));
  162.  
  163. #if 0
  164.     /* First, determine whether there is a content-type converter for this
  165.        document; if there isn't one, then the only way to handle it is to
  166.        send it straight to disk, so try that.
  167.      */
  168.     if (format_out != FO_SAVE_TO_DISK)
  169.       {
  170.         while ((cs_ptr=(net_ConverterStruct *) XP_ListNextObject(ct_list_ptr))
  171.                != 0)
  172.           {
  173.             if (format_out == cs_ptr->format_out &&
  174.                 net_compare_mime_types (URL_s->content_type,
  175.                                         cs_ptr->format_in))
  176.               break;
  177.           }
  178.  
  179.         if (! cs_ptr)  /* There is no content-type converter; save it. */
  180.           format_out = FO_SAVE_TO_DISK;
  181.         ct_list_ptr = net_converter_list;  /* Reset for next traversal. */
  182.       }
  183. #endif
  184.  
  185.  
  186.     /* if there are content encodings then
  187.      * execute decoders first
  188.      */
  189.     /* go through list of converters and choose
  190.      * the first exact or partial match we
  191.      * find.  The order of the list
  192.      * is guarenteed by the registration
  193.      * routines
  194.      *
  195.      * the format-out is compared as well.
  196.      *
  197.      * if there are transfer_encodings use them even before
  198.      * content_encodings
  199.      */
  200.     if ((URL_s->transfer_encoding && *URL_s->transfer_encoding)
  201.         || (URL_s->content_encoding && *URL_s->content_encoding))
  202.       {
  203.         char *encoding;
  204.  
  205.         if(URL_s->transfer_encoding && *URL_s->transfer_encoding)
  206.             encoding = URL_s->transfer_encoding;
  207.         else
  208.             encoding = URL_s->content_encoding;
  209.  
  210.  
  211.         while ((cs_ptr=(net_ConverterStruct *) XP_ListNextObject(ce_list_ptr))
  212.                != 0)
  213.           {
  214.             if (format_out == cs_ptr->format_out &&
  215.                 net_compare_mime_types (URL_s->content_type,
  216.                                         cs_ptr->format_in) &&
  217.                 net_compare_mime_types (encoding,
  218.                                         cs_ptr->encoding_in))
  219.               {
  220.                   net_ConverterElement *elem = XP_ListPeekTopObject(cs_ptr->converter_stack);
  221.                   XP_ASSERT(elem != (net_ConverterElement *)0);
  222.                 return ((NET_StreamClass *)
  223.                         ((*elem->converter)
  224.                          (format_out, elem->data_obj, URL_s, context)));
  225.               }
  226.           }
  227.       }
  228.  
  229.     /* now search for content-type converters
  230.      */
  231.     /* go through list of converters and choose
  232.      * the first exact or partial match we
  233.      * find.  The order of the list
  234.      * is guarenteed by the registration
  235.      * routines
  236.      */
  237.     while((cs_ptr=(net_ConverterStruct *) XP_ListNextObject(ct_list_ptr)) != 0)
  238.       {
  239.         if(format_out == cs_ptr->format_out)
  240.             if(net_compare_mime_types(URL_s->content_type, cs_ptr->format_in))
  241.               {
  242.                   net_ConverterElement *elem = XP_ListPeekTopObject(cs_ptr->converter_stack);
  243.                   XP_ASSERT(elem != (net_ConverterElement *)0);
  244.                 return( (NET_StreamClass *) (*elem->converter) (format_out, 
  245.                                                  elem->data_obj, URL_s, context));
  246.               }
  247.       }
  248.  
  249.     TRACEMSG(("Alert! did not find a converter or decoder\n"));
  250.     FE_Alert(context, XP_GetString(XP_ALERT_CANTFIND_CONVERTER));
  251.  
  252.     return(0);  /* Uh-oh. didn't find a converter.  VERY VERY BAD! */
  253. }
  254.  
  255. /* prints out all presentation mime types during successive calls
  256.  * call with first equal true to reset to the beginning
  257.  * and with first equal false to print out the next
  258.  * converter in the list.  Returns zero when all converters
  259.  * are printed out.
  260.  */
  261. MODULE_PRIVATE char *
  262. XP_ListNextPresentationType(Bool first)
  263. {
  264.     static XP_List * converter_list_ptr;
  265.     net_ConverterStruct * converter;
  266.  
  267.     /* reset list pointers 
  268.      */
  269.     if(first)
  270.         converter_list_ptr = net_converter_list[FO_PRESENT];
  271.  
  272.     /* print out next converter if there are any more
  273.      */
  274.     converter = (net_ConverterStruct *) XP_ListNextObject(converter_list_ptr);
  275.  
  276.     if(converter && converter->format_out == FO_PRESENT)
  277.     return(converter->format_in);
  278.  
  279.     /* else */
  280.     return(NULL);
  281. }
  282.     
  283. /* prints out all encoding mime types during successive calls
  284.  * call with first equal true to reset to the beginning
  285.  * and with first equal false to print out the next
  286.  * converter in the list.  Returns zero when all converters
  287.  * are printed out.
  288.  */
  289. MODULE_PRIVATE char *
  290. XP_ListNextEncodingType(Bool first)
  291. {
  292.     static XP_List * decoder_list_ptr;
  293.     net_ConverterStruct * converter;
  294.     static int count;
  295.     static index = 0;
  296.     static int curArrayIndex = 0;
  297.  
  298.  
  299.     while ((index < count) && (curArrayIndex < MAX_FORMATS_OUT)) {
  300.         decoder_list_ptr = net_decoder_list[curArrayIndex];
  301.         count = XP_ListCount(decoder_list_ptr);
  302.         if (count > 0) {
  303.             index = 0;
  304.             break;
  305.         }
  306.         else count++;
  307.     }
  308. #if 0
  309.         /* reset list pointers
  310.         */
  311.         if(first)
  312.         {
  313.             decoder_list_ptr = net_decoder_list;
  314.         }
  315. #endif
  316.     converter = (net_ConverterStruct *) XP_ListNextObject(decoder_list_ptr);
  317.     if(converter) {
  318.         index++;
  319.         return(converter->format_in);
  320.     }
  321.  
  322.     /* else */
  323.     return(NULL);
  324. }
  325.      
  326. PRIVATE void
  327. net_RegisterGenericConverterOrDecoder(XP_List        ** conv_list,
  328.                                         char            * format_in,
  329.                                         char            * encoding_in,
  330.                                         FO_Present_Types  format_out,
  331.                                         void            * data_obj,
  332.                                         NET_Converter   * converter_func)
  333. {
  334.     XP_List * list_ptr = *conv_list;
  335.     net_ConverterStruct * converter_struct_ptr;
  336.     net_ConverterStruct * new_converter_struct;
  337.     net_ConverterElement *elem;
  338.     int f_in_type;
  339.     XP_List *pList;
  340.     int i;
  341.  
  342.     if (needInit) {    /* MWH - this will only happen once. It is pretty ugly,
  343.                      but I don't know how to get around it.  */
  344.         needInit = FALSE;
  345.  
  346.         for (i = 0; i <MAX_FORMATS_OUT;i++) {
  347.             net_converter_list[i] = net_decoder_list[i] = 0;
  348.         }
  349.  
  350.     }
  351.     
  352.     pList = list_ptr = conv_list[format_out];
  353.     /* check for an existing converter with the same format_in and format_out 
  354.      * and add this to the list if found.
  355.      *
  356.      * if the list is NULL XP_ListNextObject will return 0
  357.      */
  358.     while((converter_struct_ptr = (net_ConverterStruct *) XP_ListNextObject(list_ptr)) != 0){
  359.         if(format_out == converter_struct_ptr->format_out)
  360.                     /* Rewritten to logic equivalent but should-be-faster version:*/
  361.             if (((!encoding_in && !converter_struct_ptr->encoding_in) ||
  362.                          (encoding_in && converter_struct_ptr->encoding_in &&
  363.                           !XP_STRCMP (encoding_in,
  364.                                       converter_struct_ptr->encoding_in)))
  365.                         && !strcasecomp(format_in, converter_struct_ptr->format_in)
  366. #ifdef XP_WIN
  367.                  && (converter_struct_ptr->bAutomated == autoFlag)
  368. #endif
  369.                 )
  370.                   {
  371.                   /* Add a new converter element to the list */
  372.                   net_ConverterElement *elem = XP_NEW_ZAP(net_ConverterElement);
  373.                   if( elem == (net_ConverterElement *)0 ) return;
  374.                   elem->converter = converter_func;
  375.                   elem->data_obj  = data_obj;
  376.                   XP_ListAddObject(converter_struct_ptr->converter_stack, elem);
  377.                 return;
  378.               }
  379.        }
  380.     /* find out the type of entry format_in is
  381.      */
  382.     f_in_type = NET_MIME_EXACT_MATCH;
  383.     if(XP_STRCHR(format_in, '*'))
  384.       {
  385.         if(!XP_STRCMP(format_in, "*"))
  386.             f_in_type = NET_WILDCARD_MATCH;
  387.         else
  388.             f_in_type = NET_MIME_PARTIAL_MATCH;
  389.       }
  390.  
  391.     /* if existing converter not found/replaced
  392.      * add this converter to the list in
  393.      * its order of importance so that we can
  394.      * take the first exact or partial fit
  395.      */ 
  396.     new_converter_struct = XP_NEW(net_ConverterStruct);
  397.     if(!new_converter_struct) {
  398.         return;
  399.     }
  400.  
  401.     new_converter_struct->converter_stack = XP_ListNew();
  402.     if( new_converter_struct->converter_stack == (XP_List *)0 )
  403.     {
  404.         XP_FREE(new_converter_struct);
  405.         return;
  406.     }
  407.  
  408.     elem = XP_NEW_ZAP(net_ConverterElement);
  409.     if( elem == (net_ConverterElement *)0 )
  410.     {
  411.         XP_ListDestroy(new_converter_struct->converter_stack);
  412.         XP_FREE(new_converter_struct);
  413.         return;
  414.     }
  415.  
  416.     elem->converter = converter_func;
  417.     elem->data_obj  = data_obj;
  418.     new_converter_struct->format_in = 0;
  419.     StrAllocCopy(new_converter_struct->format_in, format_in);
  420.     new_converter_struct->encoding_in = (encoding_in
  421.                                          ? XP_STRDUP (encoding_in) : 0);
  422.     new_converter_struct->format_out = format_out;
  423.     XP_ListAddObject(new_converter_struct->converter_stack, elem);
  424.     new_converter_struct->bAutomated = autoFlag;
  425.  
  426.  
  427.     if(!pList) {
  428. /*    if(!*conv_list) */
  429.         /* create the list and add this object
  430.          * don't worry about order since there
  431.          * can't be any 
  432.            */
  433.         pList = XP_ListNew();
  434.         if(!pList)
  435.             return; /* big error */
  436.         conv_list[format_out] = pList;
  437.          XP_ListAddObject(pList, new_converter_struct);
  438.         return;
  439.       }
  440.     if(f_in_type == NET_MIME_EXACT_MATCH)
  441.       {
  442. #if defined(XP_WIN) && defined (MOZILLA_CLIENT)
  443.         net_ConverterStruct *pRegistered;
  444.         XP_List *tempList = pList;
  445.         int count = XP_ListCount(tempList);
  446.         for (i = 0; i < count; i++)    {
  447.             pRegistered = (net_ConverterStruct *) XP_ListNextObject(tempList);
  448.         
  449.             if(new_converter_struct->bAutomated == TRUE)    {
  450.                 if(pRegistered->bAutomated == FALSE)    {
  451.                     XP_ListInsertObject (pList, pRegistered, new_converter_struct);
  452.                     return;
  453.                 }
  454.             }
  455.         
  456.             /* if it is an exact match type 
  457.             * then we can just add it to the beginning
  458.             * of the list
  459.             */
  460.             if (!XP_STRCASECMP(pRegistered->format_in, new_converter_struct->format_in)
  461.                 && ((!pRegistered->encoding_in && !new_converter_struct->format_in)
  462.                     || !XP_STRCASECMP(pRegistered->encoding_in, new_converter_struct->encoding_in))) {
  463.                 if(new_converter_struct->bAutomated == TRUE)    {
  464.                     XP_DELETE( new_converter_struct);
  465.                     return;
  466.                 }
  467.             
  468.                 //    Overwrite
  469.                 XP_MEMCPY(pRegistered, new_converter_struct, sizeof(net_ConverterStruct));
  470.                 XP_DELETE( new_converter_struct);
  471.                 return;
  472.             }
  473.         }
  474.  
  475.         
  476.         
  477.         
  478.         
  479.         
  480.         
  481.         XP_ListInsertObject (pList, pRegistered, new_converter_struct);
  482. #else
  483.         XP_ListAddObject(pList, new_converter_struct);
  484.         TRACEMSG(("Adding Converter to Beginning of list"));
  485. #endif
  486.       }
  487.     else if(f_in_type == NET_MIME_PARTIAL_MATCH)
  488.       {
  489.         /* this is a partial wildcard of the form (image/(star))
  490.          * store it at the end of all the exact match pairs
  491.          */
  492.         list_ptr = pList;
  493.         while((converter_struct_ptr = (net_ConverterStruct *) XP_ListNextObject(list_ptr)) != 0)
  494.           {
  495.             if(XP_STRCHR(converter_struct_ptr->format_in, '*'))
  496.               {
  497.                 XP_ListInsertObject (pList, 
  498.                                      converter_struct_ptr, 
  499.                                      new_converter_struct); 
  500.                 return;
  501.               }
  502.           }
  503.         /* else no * objects in list */
  504.         XP_ListAddObjectToEnd(pList, new_converter_struct);
  505.         TRACEMSG(("Adding Converter to Middle of list"));
  506.       }
  507.     else
  508.       {
  509.         /* this is a very wild wildcard match of the form "*" only.
  510.          * It has the least precedence so store it at the end
  511.          * of the list.  (There must always be at least one
  512.          * item in the list (see above))
  513.          */
  514.         XP_ListAddObjectToEnd(pList, new_converter_struct);
  515.         TRACEMSG(("Adding Converter to end of list"));
  516.       }
  517. }
  518.  
  519. struct net_encoding_converter
  520. {
  521.   char *encoding_in;
  522.   void *data_obj;
  523.   NET_Converter * converter_func;
  524. };
  525.  
  526. static struct net_encoding_converter *net_all_encoding_converters;
  527. static int net_encoding_converter_size = 0;
  528. static int net_encoding_converter_fp = 0;
  529.  
  530. PUBLIC void* NET_GETDataObject(XP_List* list, char* mineType, void** obj)
  531. {
  532.     net_ConverterStruct* converter_struct_ptr;
  533.  
  534.     *obj = (void*)0;
  535.  
  536.     while((converter_struct_ptr = (net_ConverterStruct *)XP_ListNextObject(list)) != NULL)    {
  537.         if(converter_struct_ptr->bAutomated)    {
  538.             if(!XP_STRCMP(converter_struct_ptr->format_in, mineType)) {
  539.                 net_ConverterElement *elem = XP_ListPeekTopObject(converter_struct_ptr->converter_stack);
  540.                 *obj = (void*)converter_struct_ptr;
  541.                 return elem->data_obj;
  542.             }
  543.         }
  544.     }
  545.  
  546.     return (void*)0;
  547. }
  548.  
  549. PUBLIC XP_List* NET_GetRegConverterList(FO_Present_Types iFormatOut)
  550. {
  551.     return     net_converter_list[iFormatOut];
  552.  
  553. }
  554. /* register a routine to decode a stream
  555.  */
  556.  
  557. PUBLIC void
  558. NET_RegisterEncodingConverter(char *encoding_in,
  559.                               void          * data_obj,
  560.                               NET_Converter * converter_func)
  561. {
  562.  
  563.     /* Don't register anything right now, but remember that we have a
  564.        decoder for this so that we can register it hither and yon at
  565.        a later date.
  566.      */
  567.   if (net_encoding_converter_size <= net_encoding_converter_fp)
  568.     {
  569.       net_encoding_converter_size += 10;
  570.       if (net_all_encoding_converters)
  571.         net_all_encoding_converters = (struct net_encoding_converter *)
  572.           XP_REALLOC (net_all_encoding_converters,
  573.                       net_encoding_converter_size
  574.                       * sizeof (*net_all_encoding_converters));
  575.       else
  576.         net_all_encoding_converters = (struct net_encoding_converter *)
  577.           XP_CALLOC (net_encoding_converter_size,
  578.                      sizeof (*net_all_encoding_converters));
  579.     }
  580.  
  581.   { /* Don't register the same decoder twice; having too many
  582.        decoders is multiplicatively expensive with respect to the number of
  583.        calls to net_RegisterGenericConverterOrDecoder() from the loop inside
  584.        NET_RegisterAllEncodingConverters(). */
  585.       int i;
  586.       for (i=0; i < net_encoding_converter_fp; i++)
  587.       if ((net_all_encoding_converters[i].data_obj == data_obj)
  588.               && (net_all_encoding_converters[i].converter_func == converter_func)
  589.               && (strcmp(net_all_encoding_converters[i].encoding_in,encoding_in)
  590.                   == 0))
  591.               return;
  592.   }
  593.   
  594.   net_all_encoding_converters [net_encoding_converter_fp].encoding_in
  595.     = XP_STRDUP (encoding_in);
  596.   net_all_encoding_converters [net_encoding_converter_fp].data_obj
  597.     = data_obj;
  598.   net_all_encoding_converters [net_encoding_converter_fp].converter_func
  599.     = converter_func;
  600.   net_encoding_converter_fp++;
  601.  
  602. }
  603.  
  604.  
  605. #ifdef MOZILLA_CLIENT
  606.  
  607. void
  608. NET_DumpDecoders()
  609. {
  610. #ifdef DEBUG
  611.     net_ConverterStruct * cs_ptr;
  612.     XP_List *list_ptr = net_decoder_list[FO_PRESENT];
  613.  
  614.     while((cs_ptr = XP_ListNextObject(list_ptr)))
  615.     {
  616.         char *msg = PR_smprintf("in: %s  out: %d\n",cs_ptr->encoding_in, cs_ptr->format_out);
  617.  
  618.         FE_Trace(msg);
  619.         FREE(msg);
  620.     }
  621. #endif /* DEBUG */
  622. }
  623.  
  624. /* register an encoding converter that is used for everything,
  625.  * no exeptions
  626.  * this is necessary for chunked encoding
  627.  */
  628. MODULE_PRIVATE void
  629. NET_RegisterUniversalEncodingConverter(char *encoding_in,
  630.                               void          * data_obj,
  631.                               NET_Converter * converter_func)
  632. {
  633.     int i;
  634.  
  635.     /* for each list in the net_decoder_list array */
  636.     for (i = 0; i < MAX_FORMATS_OUT; i++) 
  637.     {
  638.  
  639.       net_RegisterGenericConverterOrDecoder (net_decoder_list,
  640.                                              "*",
  641.                                              encoding_in,
  642.                                              i,  /* format out */
  643.                                              data_obj,
  644.                                              converter_func);
  645.     }
  646.  
  647. }
  648.  
  649. void
  650. NET_RegisterAllEncodingConverters (char *format_in,
  651.                                    FO_Present_Types format_out)
  652. {
  653.   int i;
  654.  
  655. #ifdef XP_UNIX
  656.   /* On Unix at least, there should be *some* registered... */
  657.   assert (net_encoding_converter_fp > 0);
  658. #endif
  659.  
  660.   for (i = 0; i < net_encoding_converter_fp; i++)
  661.     {
  662.       char *encoding_in = net_all_encoding_converters [i].encoding_in;
  663.       void *data_obj = net_all_encoding_converters [i].data_obj;
  664.       NET_Converter *func = net_all_encoding_converters [i].converter_func;
  665.  
  666.       net_RegisterGenericConverterOrDecoder (net_decoder_list,
  667.                                              format_in,
  668.                                              encoding_in,
  669.                                              format_out,
  670.                                              data_obj,
  671.                                              func);
  672.  
  673.       net_RegisterGenericConverterOrDecoder (net_decoder_list,
  674.                                              format_in,
  675.                                              encoding_in,
  676.                                              format_out | FO_CACHE_ONLY,
  677.                                              data_obj,
  678.                                              func);
  679.  
  680.       net_RegisterGenericConverterOrDecoder (net_decoder_list,
  681.                                              format_in,
  682.                                              encoding_in,
  683.                                              format_out | FO_ONLY_FROM_CACHE,
  684.                                              data_obj,
  685.                                              func);
  686.     }
  687. }
  688.  
  689. #endif /* MOZILLA_CLIENT */
  690.  
  691.  
  692.  
  693.  
  694. /*  Register a routine to convert between format_in and format_out
  695.  *
  696.  */
  697. #if (defined(XP_WIN) || defined(XP_OS2)) && defined(MOZILLA_CLIENT) 
  698. /*  Reroute XP handling of streams for windows.
  699.  *  MWH -This routine should not be called recursively.  
  700.     It rely on the static global variable to set the  
  701.  *    automatedflag. The reason for this is I don't to 
  702.     change the API on NET_RegisterContentTypeConverter
  703.     because    some other group still depend on this API.
  704.  *    If we decided to change API, please fix this.
  705.  */
  706. PUBLIC void
  707. NET_RegContentTypeConverter (char * format_in,
  708.                              FO_Present_Types format_out,
  709.                              void          * data_obj,
  710.                              NET_Converter * converter_func,
  711.                              Bool          bAutomated)
  712. {
  713.     autoFlag = bAutomated;
  714.     NET_RegisterContentTypeConverter (format_in, format_out,
  715.                                             data_obj,
  716.                                             converter_func);
  717.     autoFlag = FALSE;
  718.  
  719. }
  720.  
  721. #endif
  722.  
  723. PUBLIC void
  724. NET_RegisterContentTypeConverter (char          * format_in,
  725.                                   FO_Present_Types format_out,
  726.                                   void          * data_obj,
  727.                                   NET_Converter * converter_func)
  728. {
  729.     net_RegisterGenericConverterOrDecoder(net_converter_list,
  730.                                           format_in,
  731.                                           0,
  732.                                           format_out,
  733.                                           data_obj,
  734.                                           converter_func);
  735.  
  736. }
  737.  
  738. #ifdef MOZILLA_CLIENT
  739. #ifdef XP_UNIX
  740.  
  741. /* register a mime type and a command to be executed
  742.  *
  743.  * if stream_block_size is zero then the data will be completely
  744.  * pooled and the external viewer called with the filename.
  745.  * if stream_block_size is greater than zero the viewer will be
  746.  * called with a popen and the stream_block_size will be used as
  747.  * the maximum size of each buffer to pass to the viewer
  748.  */
  749. MODULE_PRIVATE void
  750. NET_RegisterExternalViewerCommand(char * format_in, 
  751.                                   char * system_command, 
  752.                                   unsigned int stream_block_size) 
  753. {
  754.     CV_ExtViewStruct * new_obj = XP_NEW(CV_ExtViewStruct);
  755.  
  756.     if(!new_obj)
  757.        return;
  758.  
  759.     XP_MEMSET(new_obj, 0, sizeof(CV_ExtViewStruct));
  760.  
  761.     /* make a new copy of the command so it can be passed
  762.      * as the data object
  763.      */
  764.     StrAllocCopy(new_obj->system_command, system_command);
  765.     new_obj->stream_block_size = stream_block_size;
  766.  
  767.     /* register the routine
  768.      */
  769.     NET_RegisterContentTypeConverter(format_in, FO_PRESENT, new_obj, NET_ExtViewerConverter);
  770.  
  771.     /* Also register the content-encoding converters on this type.
  772.        Those get registered only on types we know how to display,
  773.        either internally or externally - and don't get registered
  774.        on types we know nothing about.
  775.      */
  776.     NET_RegisterAllEncodingConverters (format_in, FO_PRESENT);
  777. }
  778.  
  779. /* removes all external viewer commands
  780.  */
  781. PUBLIC void
  782. NET_ClearExternalViewerConverters(void)
  783. {
  784.     XP_List * list_ptr;
  785.     XP_List *temp_list;
  786.     net_ConverterStruct * converter_struct_ptr;
  787.     int i;
  788.  
  789.     for (i = 0; i <MAX_FORMATS_OUT; i++) { 
  790.         list_ptr = temp_list = net_converter_list[i];
  791.         while((converter_struct_ptr = 
  792.                         (net_ConverterStruct *) XP_ListNextObject(list_ptr)))
  793.       {
  794.           XP_List *new;
  795.           net_ConverterElement *elem;
  796.           
  797.           new = XP_ListNew();
  798.           if( new == (XP_List *)0 ) return;
  799.  
  800.           while( (elem = XP_ListRemoveTopObject(converter_struct_ptr->converter_stack)) != (net_ConverterElement *)0 )
  801.           {
  802.               if( elem->converter == NET_ExtViewerConverter )
  803.               {
  804.                   XP_FREEIF(elem->data_obj);
  805.                   XP_FREE(elem);
  806.                   continue;
  807.               }
  808.               else
  809.               {
  810.                   XP_ListAddObjectToEnd(new, elem);
  811.               }
  812.           }
  813.  
  814.           XP_ListDestroy(converter_struct_ptr->converter_stack);
  815.  
  816.           if( XP_ListCount(new) > 0 )
  817.           {
  818.               converter_struct_ptr->converter_stack = new;
  819.           }
  820.           else
  821.           {
  822.               XP_ListDestroy(new);
  823.               XP_ListRemoveObject(temp_list, converter_struct_ptr);
  824.               XP_FREE(converter_struct_ptr->format_in);
  825.               XP_FREE(converter_struct_ptr->encoding_in);
  826.               XP_FREE(converter_struct_ptr);
  827.           }
  828.       }
  829.     }
  830. }
  831.  
  832. /* removes all registered converters of any kind
  833.  */
  834. PUBLIC void
  835. NET_ClearAllConverters(void)
  836. {
  837.     net_ConverterStruct * aConverter;
  838.     net_ConverterElement * aConverterStackElement;
  839.     XP_List * theList;
  840.     int i;
  841.  
  842.     /* for each list in the net_converter_list array */
  843.     for (i = 0; i < MAX_FORMATS_OUT; i++) {
  844.         theList = net_converter_list[i];
  845.         /* Traverse it, removing each converter, and freeing its data */
  846.         while( aConverter = (net_ConverterStruct *) XP_ListRemoveTopObject(theList) )
  847.         {
  848.             FREEIF(aConverter->format_in);
  849.             FREEIF(aConverter->encoding_in);
  850.             while( aConverterStackElement = XP_ListRemoveTopObject(aConverter->converter_stack) )
  851.             {
  852. #ifdef XP_UNIX
  853.                 if(aConverterStackElement->converter == NET_ExtViewerConverter)
  854.                     XP_FREEIF(aConverterStackElement->data_obj);
  855. #endif /* XP_UNIX */
  856.                 XP_FREE(aConverterStackElement);
  857.             }
  858.             XP_ListDestroy(aConverter->converter_stack);
  859.             FREE(aConverter);
  860.         }
  861.         XP_ListDestroy(theList);
  862.         net_converter_list[i] = NULL;
  863.     }
  864.  
  865.     /* for each list in the net_decoder_list array */
  866.     for (i = 0; i < MAX_FORMATS_OUT; i++) {
  867.         theList = net_decoder_list[i];
  868.         /* Traverse it, removing each converter, and freeing its data */
  869.         while( aConverter = (net_ConverterStruct *) XP_ListRemoveTopObject(theList) )
  870.         {
  871.             FREEIF(aConverter->format_in);
  872.             FREEIF(aConverter->encoding_in);
  873.             while( aConverterStackElement = XP_ListRemoveTopObject(aConverter->converter_stack) )
  874.             {
  875. #ifdef XP_UNIX
  876.                 if(aConverterStackElement->converter == NET_ExtViewerConverter)
  877.                     XP_FREEIF(aConverterStackElement->data_obj);
  878. #endif /* XP_UNIX */
  879.                 XP_FREE(aConverterStackElement);
  880.             }
  881.             XP_ListDestroy(aConverter->converter_stack);
  882.             FREE(aConverter);
  883.         }
  884.         XP_ListDestroy(theList);
  885.         net_decoder_list[i] = NULL;
  886.     }
  887. }
  888.  
  889. /* register an external program to act as a content-type
  890.  * converter
  891.  */
  892. MODULE_PRIVATE void
  893. NET_RegisterExternalConverterCommand(char * format_in,
  894.                                      int    format_out,
  895.                                      char * system_command,
  896.                                      char * new_format)
  897. {
  898.     CV_ExtConverterStruct * new_obj = XP_NEW(CV_ExtConverterStruct);
  899.  
  900.     if(!new_obj)
  901.        return;
  902.  
  903.     memset(new_obj, 0, sizeof(CV_ExtConverterStruct));
  904.  
  905.     /* make a new copy of the command so it can be passed
  906.      * as the data object
  907.      */
  908.     StrAllocCopy(new_obj->command,    system_command);
  909.     StrAllocCopy(new_obj->new_format, new_format);
  910.     new_obj->is_encoding_converter = FALSE;
  911.  
  912.     /* register the routine
  913.      */
  914.     NET_RegisterContentTypeConverter(format_in, format_out, new_obj, NET_ExtConverterConverter);
  915. }
  916.  
  917. /* register an external program to act as a content-encoding
  918.  * converter
  919.  */
  920. PUBLIC void
  921. NET_RegisterExternalDecoderCommand (char * format_in,
  922.                                     char * format_out,
  923.                                     char * system_command)
  924. {
  925.     CV_ExtConverterStruct * new_obj = XP_NEW(CV_ExtConverterStruct);
  926.  
  927.     if(!new_obj)
  928.        return;
  929.  
  930.     memset(new_obj, 0, sizeof(CV_ExtConverterStruct));
  931.  
  932.     /* make a new copy of the command so it can be passed
  933.      * as the data object
  934.      */
  935.     StrAllocCopy(new_obj->command,    system_command);
  936.     StrAllocCopy(new_obj->new_format, format_out);
  937.     new_obj->is_encoding_converter = TRUE;
  938.  
  939.     /* register the routine
  940.      */
  941.     NET_RegisterEncodingConverter (format_in, new_obj,
  942.                                    NET_ExtConverterConverter);
  943. }
  944.  
  945.  
  946. #endif /* XP_UNIX */
  947. #endif /* MOZILLA_CLIENT */
  948.  
  949. /*  NewMKStream
  950.  *  Utility to create a new initialized NET_StreamClass object
  951.  */
  952. NET_StreamClass *
  953. NET_NewStream          (char                 *name,
  954.                         MKStreamWriteFunc     process,
  955.                         MKStreamCompleteFunc  complete,
  956.                         MKStreamAbortFunc     abort,
  957.                         MKStreamWriteReadyFunc ready,
  958.                         void                 *streamData,
  959.                         MWContext            *windowID)
  960. {
  961.     NET_StreamClass *stream = XP_NEW (NET_StreamClass);
  962.     if (!stream)
  963.         return nil;
  964.         
  965.     stream->name        = name;
  966.     stream->window_id   = windowID;
  967.     stream->data_object = streamData;
  968.     stream->put_block   = process;
  969.     stream->complete    = complete;
  970.     stream->abort       = abort;
  971.     stream->is_write_ready = ready;
  972.     
  973.     return stream;
  974. }
  975.  
  976. PRIVATE XP_List *
  977. net_GetConverterOrDecoderList(XP_List        ** conv_list,
  978.                               char            * format_in,
  979.                               char            * encoding_in,
  980.                               FO_Present_Types  format_out)
  981. {
  982.     XP_List * list_ptr = *conv_list;
  983.     net_ConverterStruct * converter_struct_ptr;
  984.  
  985.     while((converter_struct_ptr = (net_ConverterStruct *) XP_ListNextObject(list_ptr)) != 0)
  986.         if(format_out == converter_struct_ptr->format_out)
  987.             if(!strcasecomp(format_in, converter_struct_ptr->format_in)
  988.                && ((!encoding_in && !converter_struct_ptr->encoding_in) ||
  989.                    (encoding_in && converter_struct_ptr->encoding_in &&
  990.                     !XP_STRCMP (encoding_in,
  991.                                 converter_struct_ptr->encoding_in)))
  992.                )
  993.               {
  994.                   return converter_struct_ptr->converter_stack;
  995.               }
  996.  
  997.     return (XP_List *)0;
  998. }
  999.  
  1000. PUBLIC void
  1001. NET_DeregisterContentTypeConverter(char *format_in, FO_Present_Types format_out)
  1002. {
  1003.     XP_List *l;
  1004.     net_ConverterElement *elem;
  1005.  
  1006.     l = net_GetConverterOrDecoderList(net_converter_list, format_in, 0, format_out);
  1007.     if( l == (XP_List *)0 ) return;
  1008.  
  1009.     elem = XP_ListRemoveTopObject(l);
  1010. # ifdef XP_UNIX
  1011.     /* total kludge!! */
  1012.     if( elem->converter == NET_ExtViewerConverter )
  1013.         XP_FREEIF(elem->data_obj);
  1014. # endif
  1015.     XP_FREE(elem);
  1016.  
  1017.     return;
  1018. }
  1019.  
  1020. #ifdef DEBUG
  1021. MODULE_PRIVATE void
  1022. NET_DisplayStreamInfoAsHTML(ActiveEntry *cur_entry)
  1023. {
  1024.     char *buffer = (char*)XP_ALLOC(2048);
  1025.        NET_StreamClass * stream;
  1026.     int i;
  1027.  
  1028.     static char *fo_names[] = {
  1029.         /*  0 */    "(unknown)",
  1030.         /*  1 */    "Present",
  1031.         /*  2 */    "Internal Image",
  1032.         /*  3 */    "Out to Proxy Client",
  1033.         /*  4 */    "View Source",
  1034.         /*  5 */    "Save As",
  1035.         /*  6 */    "Save as Text",
  1036.         /*  7 */    "Save as Postscript",
  1037.         /*  8 */    "Quote Message",
  1038.         /*  9 */    "Mail To",
  1039.         /* 10 */    "OLE Network",
  1040.         /* 11 */    "Multipart Image",
  1041.         /* 12 */    "Embed",
  1042.         /* 13 */    "Print",
  1043.         /* 14 */    "Plugin",
  1044.         /* 15 */    "Append to Folder",
  1045.         /* 16 */    "Do Java",
  1046.         /* 17 */    "Byterange",
  1047.         /* 18 */    "EDT Save Image",
  1048.         /* 19 */    "Edit",
  1049.         /* 20 */    "Load HTML Help Map File",
  1050.         /* 21 */    "Save to News DB",
  1051.         /* 22 */    "Open Draft",
  1052.     };
  1053.  
  1054.     if(!buffer)
  1055.       {
  1056.         cur_entry->status = MK_UNABLE_TO_CONVERT;
  1057.         return;
  1058.       }
  1059.  
  1060.     StrAllocCopy(cur_entry->URL_s->content_type, TEXT_HTML);
  1061.  
  1062.     cur_entry->format_out = CLEAR_CACHE_BIT(cur_entry->format_out);
  1063.     stream = NET_StreamBuilder(cur_entry->format_out, 
  1064.                                cur_entry->URL_s, 
  1065.                                cur_entry->window_id);
  1066.  
  1067.     if(!stream)
  1068.       {
  1069.         cur_entry->status = MK_UNABLE_TO_CONVERT;
  1070.         FREE(buffer);
  1071.         return;
  1072.       }
  1073.  
  1074.  
  1075.     /* define a macro to push a string up the stream
  1076.      * and handle errors
  1077.      */
  1078. #define PUT_PART(part)                                                    \
  1079. cur_entry->status = (*stream->put_block)(stream,            \
  1080.                                         part ? part : "Unknown",        \
  1081.                                         part ? XP_STRLEN(part) : 7);    \
  1082. if(cur_entry->status < 0)                                                \
  1083.   goto END;
  1084.  
  1085.     XP_SPRINTF(buffer, 
  1086. "<html><head><title>Information about the Netscape streams configuration</title></head>\n"
  1087. "<body><h1>Information about the Netscape streams configuration</h1>\n"
  1088. );
  1089.  
  1090.     PUT_PART(buffer);
  1091.  
  1092.     XP_SPRINTF(buffer, "<h2>Converter List</h2>\n<ul>");
  1093.     PUT_PART(buffer);
  1094.  
  1095.     for( i = 0; i < MAX_FORMATS_OUT; i++ )
  1096.     {
  1097.         int j = i & ~FO_CACHE_ONLY & ~FO_ONLY_FROM_CACHE;
  1098.         XP_List *list_ptr = net_converter_list[i];
  1099.         net_ConverterStruct *csp;
  1100.  
  1101.         XP_SPRINTF(buffer, "<li><ul><p>%s", j < sizeof(fo_names)/sizeof(fo_names[0]) ? 
  1102.                    fo_names[j] : fo_names[0]);
  1103.         PUT_PART(buffer);
  1104.  
  1105.         if( i & FO_CACHE_ONLY )
  1106.         {
  1107.             XP_SPRINTF(buffer, " | Cache Only");
  1108.             PUT_PART(buffer);
  1109.         }
  1110.  
  1111.         if( i & FO_ONLY_FROM_CACHE )
  1112.         {
  1113.             XP_SPRINTF(buffer, " | Only From Cache");
  1114.             PUT_PART(buffer);
  1115.         }
  1116.  
  1117.         XP_SPRINTF(buffer, " [%d]</p>", i);
  1118.         PUT_PART(buffer);
  1119.  
  1120.         while( (csp = (net_ConverterStruct *)XP_ListNextObject(list_ptr)) != (net_ConverterStruct *)0 )
  1121.         {
  1122.             XP_List *stack = csp->converter_stack;
  1123.             int stack_count = XP_ListCount(stack);
  1124.             net_ConverterElement *elem;
  1125.             XP_SPRINTF(buffer, 
  1126.                        "<li>Format In: %s<br>\n"
  1127.                        "Encoding In: %s<br>\n"
  1128.                        "Present Type: %d<br>\n"
  1129.                        "%s<br><ol>%d Converter%s:\n",
  1130.                        (csp->format_in != (char *)0) ? csp->format_in : "(null)",
  1131.                        (csp->encoding_in != (char *)0) ? csp->encoding_in : "(null)",
  1132.                        csp->format_out,
  1133.                        csp->bAutomated ? "Automated" : "Not automated",
  1134.                        stack_count, (stack_count == 1) ? "" : "s");
  1135.             PUT_PART(buffer);
  1136.  
  1137.             while( (elem = (net_ConverterElement *)XP_ListNextObject(stack)) != (net_ConverterElement *)0 )
  1138.             {
  1139. #if defined(__sun) && !defined(SVR4)
  1140.                 XP_SPRINTF(buffer, "<li>Converter: %lu<br>Object: %lu<br>\n",
  1141.                            elem->converter, elem->data_obj);
  1142. #else
  1143.                 XP_SPRINTF(buffer, "<li>Converter: %p<br>Object: %p<br>\n",
  1144.                            elem->converter, elem->data_obj);
  1145. #endif
  1146.                 PUT_PART(buffer);
  1147.             }
  1148.  
  1149.             XP_SPRINTF(buffer, "<br></ol>\n");
  1150.             PUT_PART(buffer);
  1151.         }
  1152.  
  1153.         XP_SPRINTF(buffer, "</ul>\n");
  1154.         PUT_PART(buffer);
  1155.     }
  1156.  
  1157.     XP_SPRINTF(buffer, "</ul>\n");
  1158.     PUT_PART(buffer);
  1159.  
  1160.     XP_SPRINTF(buffer, "<h2>Decoder List</h2>\n<ul>");
  1161.     PUT_PART(buffer);
  1162.  
  1163.     for( i = 0; i < MAX_FORMATS_OUT; i++ )
  1164.     {
  1165.         int j = i & ~FO_CACHE_ONLY & ~FO_ONLY_FROM_CACHE;
  1166.         XP_List *list_ptr = net_decoder_list[i];
  1167.         net_ConverterStruct *csp;
  1168.  
  1169.         XP_SPRINTF(buffer, "<li><ul><p>%s", j < sizeof(fo_names)/sizeof(fo_names[0]) ? 
  1170.                    fo_names[j] : fo_names[0]);
  1171.         PUT_PART(buffer);
  1172.  
  1173.         if( i & FO_CACHE_ONLY )
  1174.         {
  1175.             XP_SPRINTF(buffer, " | Cache Only");
  1176.             PUT_PART(buffer);
  1177.         }
  1178.  
  1179.         if( i & FO_ONLY_FROM_CACHE )
  1180.         {
  1181.             XP_SPRINTF(buffer, " | Only From Cache");
  1182.             PUT_PART(buffer);
  1183.         }
  1184.  
  1185.         XP_SPRINTF(buffer, " [%d]</p>", i);
  1186.         PUT_PART(buffer);
  1187.  
  1188.         while( (csp = (net_ConverterStruct *)XP_ListNextObject(list_ptr)) != (net_ConverterStruct *)0 )
  1189.         {
  1190.             XP_List *stack = csp->converter_stack;
  1191.             int stack_count = XP_ListCount(stack);
  1192.             net_ConverterElement *elem;
  1193.             XP_SPRINTF(buffer, 
  1194.                        "<li>Format In: %s<br>\n"
  1195.                        "Encoding In: %s<br>\n"
  1196.                        "Present Type: %d<br>\n"
  1197.                        "%s<br><ol>%d Converters:\n",
  1198.                        (csp->format_in != (char *)0) ? csp->format_in : "(null)",
  1199.                        (csp->encoding_in != (char *)0) ? csp->encoding_in : "(null)",
  1200.                        csp->format_out,
  1201.                        csp->bAutomated ? "Automated" : "Not automated",
  1202.                        stack_count, (stack_count == 1) ? "" : "s");
  1203.             PUT_PART(buffer);
  1204.  
  1205.             while( (elem = (net_ConverterElement *)XP_ListNextObject(stack)) != (net_ConverterElement *)0 )
  1206.             {
  1207. #if defined(__sun) && !defined(SVR4)
  1208.                 XP_SPRINTF(buffer, "<li>Converter: %lu<br>Object: %lu<br>\n",
  1209.                            elem->converter, elem->data_obj);
  1210. #else
  1211.                 XP_SPRINTF(buffer, "<li>Converter: %p<br>Object: %p<br>\n",
  1212.                            elem->converter, elem->data_obj);
  1213. #endif
  1214.                 PUT_PART(buffer);
  1215.             }
  1216.  
  1217.             XP_SPRINTF(buffer, "<br></ol>\n");
  1218.             PUT_PART(buffer);
  1219.         }
  1220.  
  1221.         XP_SPRINTF(buffer, "</ul>\n");
  1222.         PUT_PART(buffer);
  1223.     }
  1224.  
  1225.     XP_SPRINTF(buffer, "</ul>\n");
  1226.     PUT_PART(buffer);
  1227.  
  1228.   END:
  1229.     XP_FREE(buffer);
  1230.     if( cur_entry->status < 0 )
  1231.         (*stream->abort)(stream, cur_entry->status);
  1232.     else
  1233.         (*stream->complete)(stream);
  1234.  
  1235.     return;
  1236. }
  1237.  
  1238. #endif /* DEBUG */
  1239.  
  1240. #ifdef PROFILE
  1241. #pragma profile off
  1242. #endif
  1243.  
  1244.