netwerk/streamconv/src/nsStreamConverterService.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  *
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  *
     7  *
     8  * This Original Code has been modified by IBM Corporation.
     9  * Modifications made by IBM described herein are
    10  * Copyright (c) International Business Machines
    11  * Corporation, 2000
    12  *
    13  * Modifications to Mozilla code or documentation
    14  * identified per MPL Section 3.3
    15  *
    16  * Date         Modified by     Description of modification
    17  * 03/27/2000   IBM Corp.       Added PR_CALLBACK for Optlink
    18  *                               use in OS2
    19  */
    21 #include "nsStreamConverterService.h"
    22 #include "nsIComponentRegistrar.h"
    23 #include "nsAutoPtr.h"
    24 #include "nsString.h"
    25 #include "nsIAtom.h"
    26 #include "nsDeque.h"
    27 #include "nsIInputStream.h"
    28 #include "nsIStreamConverter.h"
    29 #include "nsICategoryManager.h"
    30 #include "nsXPCOM.h"
    31 #include "nsISupportsPrimitives.h"
    32 #include "nsCOMArray.h"
    33 #include "nsTArray.h"
    34 #include "nsServiceManagerUtils.h"
    35 #include "nsHashtable.h"
    36 #include "nsISimpleEnumerator.h"
    38 ///////////////////////////////////////////////////////////////////
    39 // Breadth-First-Search (BFS) algorithm state classes and types.
    41 // Adjacency list data class.
    42 typedef nsCOMArray<nsIAtom> SCTableData;
    44 // Delete all the entries in the adjacency list
    45 static bool DeleteAdjacencyEntry(nsHashKey *aKey, void *aData, void* closure) {
    46     SCTableData *entry = (SCTableData*)aData;
    47     delete entry;
    48     return true;
    49 }
    51 // Used to establish discovered verticies.
    52 enum BFScolors {white, gray, black};
    54 // BFS hashtable data class.
    55 struct BFSTableData {
    56     nsCStringKey *key;
    57     BFScolors color;
    58     int32_t distance;
    59     nsAutoPtr<nsCStringKey> predecessor;
    61     explicit BFSTableData(nsCStringKey* aKey)
    62       : key(aKey), color(white), distance(-1)
    63     {
    64     }
    65 };
    67 ////////////////////////////////////////////////////////////
    68 // nsISupports methods
    69 NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
    72 ////////////////////////////////////////////////////////////
    73 // nsIStreamConverterService methods
    75 ////////////////////////////////////////////////////////////
    76 // nsStreamConverterService methods
    77 nsStreamConverterService::nsStreamConverterService()
    78   : mAdjacencyList(nullptr, nullptr, DeleteAdjacencyEntry, nullptr)
    79 {
    80 }
    82 nsStreamConverterService::~nsStreamConverterService() {
    83 }
    85 // Builds the graph represented as an adjacency list (and built up in
    86 // memory using an nsObjectHashtable and nsISupportsArray combination).
    87 //
    88 // :BuildGraph() consults the category manager for all stream converter
    89 // CONTRACTIDS then fills the adjacency list with edges.
    90 // An edge in this case is comprised of a FROM and TO MIME type combination.
    91 //
    92 // CONTRACTID format:
    93 // @mozilla.org/streamconv;1?from=text/html&to=text/plain
    94 // XXX curently we only handle a single from and to combo, we should repeat the
    95 // XXX registration process for any series of from-to combos.
    96 // XXX can use nsTokenizer for this.
    97 //
    99 nsresult
   100 nsStreamConverterService::BuildGraph() {
   102     nsresult rv;
   104     nsCOMPtr<nsICategoryManager> catmgr(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
   105     if (NS_FAILED(rv)) return rv;
   107     nsCOMPtr<nsISimpleEnumerator> entries;
   108     rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY, getter_AddRefs(entries));
   109     if (NS_FAILED(rv)) return rv;
   111     // go through each entry to build the graph
   112     nsCOMPtr<nsISupports> supports;
   113     nsCOMPtr<nsISupportsCString> entry;
   114     rv = entries->GetNext(getter_AddRefs(supports));
   115     while (NS_SUCCEEDED(rv)) {
   116         entry = do_QueryInterface(supports);
   118         // get the entry string
   119         nsAutoCString entryString;
   120         rv = entry->GetData(entryString);
   121         if (NS_FAILED(rv)) return rv;
   123         // cobble the entry string w/ the converter key to produce a full contractID.
   124         nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY);
   125         contractID.Append(entryString);
   127         // now we've got the CONTRACTID, let's parse it up.
   128         rv = AddAdjacency(contractID.get());
   129         if (NS_FAILED(rv)) return rv;
   131         rv = entries->GetNext(getter_AddRefs(supports));
   132     }
   134     return NS_OK;
   135 }
   138 // XXX currently you can not add the same adjacency (i.e. you can't have multiple
   139 // XXX stream converters registering to handle the same from-to combination. It's
   140 // XXX not programatically prohibited, it's just that results are un-predictable
   141 // XXX right now.
   142 nsresult
   143 nsStreamConverterService::AddAdjacency(const char *aContractID) {
   144     nsresult rv;
   145     // first parse out the FROM and TO MIME-types.
   147     nsAutoCString fromStr, toStr;
   148     rv = ParseFromTo(aContractID, fromStr, toStr);
   149     if (NS_FAILED(rv)) return rv;
   151     // Each MIME-type is a vertex in the graph, so first lets make sure
   152     // each MIME-type is represented as a key in our hashtable.
   154     nsCStringKey fromKey(fromStr);
   155     SCTableData *fromEdges = (SCTableData*)mAdjacencyList.Get(&fromKey);
   156     if (!fromEdges) {
   157         // There is no fromStr vertex, create one.
   158         fromEdges = new SCTableData();
   159         mAdjacencyList.Put(&fromKey, fromEdges);
   160     }
   162     nsCStringKey toKey(toStr);
   163     if (!mAdjacencyList.Get(&toKey)) {
   164         // There is no toStr vertex, create one.
   165         mAdjacencyList.Put(&toKey, new SCTableData());
   166     }
   168     // Now we know the FROM and TO types are represented as keys in the hashtable.
   169     // Let's "connect" the verticies, making an edge.
   171     nsCOMPtr<nsIAtom> vertex = do_GetAtom(toStr);
   172     if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
   174     NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
   175     if (!fromEdges)
   176         return NS_ERROR_FAILURE;
   178     return fromEdges->AppendObject(vertex) ? NS_OK : NS_ERROR_FAILURE;
   179 }
   181 nsresult
   182 nsStreamConverterService::ParseFromTo(const char *aContractID, nsCString &aFromRes, nsCString &aToRes) {
   184     nsAutoCString ContractIDStr(aContractID);
   186     int32_t fromLoc = ContractIDStr.Find("from=");
   187     int32_t toLoc   = ContractIDStr.Find("to=");
   188     if (-1 == fromLoc || -1 == toLoc ) return NS_ERROR_FAILURE;
   190     fromLoc = fromLoc + 5;
   191     toLoc = toLoc + 3;
   193     nsAutoCString fromStr, toStr;
   195     ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
   196     ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
   198     aFromRes.Assign(fromStr);
   199     aToRes.Assign(toStr);
   201     return NS_OK;
   202 }
   204 // nsObjectHashtable enumerator functions.
   206 // Initializes the BFS state table.
   207 static bool InitBFSTable(nsHashKey *aKey, void *aData, void* closure) {
   208     NS_ASSERTION((SCTableData*)aData, "no data in the table enumeration");
   210     nsHashtable *BFSTable = (nsHashtable*)closure;
   211     if (!BFSTable) return false;
   213     BFSTable->Put(aKey, new BFSTableData(static_cast<nsCStringKey*>(aKey)));
   214     return true;
   215 }
   217 // cleans up the BFS state table
   218 static bool DeleteBFSEntry(nsHashKey *aKey, void *aData, void *closure) {
   219     BFSTableData *data = (BFSTableData*)aData;
   220     data->key = nullptr;
   221     delete data;
   222     return true;
   223 }
   225 class CStreamConvDeallocator : public nsDequeFunctor {
   226 public:
   227     virtual void* operator()(void* anObject) {
   228         nsCStringKey *key = (nsCStringKey*)anObject;
   229         delete key;
   230         return 0;
   231     }
   232 };
   234 // walks the graph using a breadth-first-search algorithm which generates a discovered
   235 // verticies tree. This tree is then walked up (from destination vertex, to origin vertex)
   236 // and each link in the chain is added to an nsStringArray. A direct lookup for the given
   237 // CONTRACTID should be made prior to calling this method in an attempt to find a direct
   238 // converter rather than walking the graph.
   239 nsresult
   240 nsStreamConverterService::FindConverter(const char *aContractID, nsTArray<nsCString> **aEdgeList) {
   241     nsresult rv;
   242     if (!aEdgeList) return NS_ERROR_NULL_POINTER;
   243     *aEdgeList = nullptr;
   245     // walk the graph in search of the appropriate converter.
   247     int32_t vertexCount = mAdjacencyList.Count();
   248     if (0 >= vertexCount) return NS_ERROR_FAILURE;
   250     // Create a corresponding color table for each vertex in the graph.
   251     nsObjectHashtable lBFSTable(nullptr, nullptr, DeleteBFSEntry, nullptr);
   252     mAdjacencyList.Enumerate(InitBFSTable, &lBFSTable);
   254     NS_ASSERTION(lBFSTable.Count() == vertexCount, "strmconv BFS table init problem");
   256     // This is our source vertex; our starting point.
   257     nsAutoCString fromC, toC;
   258     rv = ParseFromTo(aContractID, fromC, toC);
   259     if (NS_FAILED(rv)) return rv;
   261     nsCStringKey *source = new nsCStringKey(fromC.get());
   263     BFSTableData *data = (BFSTableData*)lBFSTable.Get(source);
   264     if (!data) {
   265         delete source;
   266         return NS_ERROR_FAILURE;
   267     }
   269     data->color = gray;
   270     data->distance = 0;
   271     CStreamConvDeallocator *dtorFunc = new CStreamConvDeallocator();
   273     nsDeque grayQ(dtorFunc);
   275     // Now generate the shortest path tree.
   276     grayQ.Push(source);
   277     while (0 < grayQ.GetSize()) {
   278         nsCStringKey *currentHead = (nsCStringKey*)grayQ.PeekFront();
   279         SCTableData *data2 = (SCTableData*)mAdjacencyList.Get(currentHead);
   280         if (!data2) return NS_ERROR_FAILURE;
   282         // Get the state of the current head to calculate the distance of each
   283         // reachable vertex in the loop.
   284         BFSTableData *headVertexState = (BFSTableData*)lBFSTable.Get(currentHead);
   285         if (!headVertexState) return NS_ERROR_FAILURE;
   287         int32_t edgeCount = data2->Count();
   289         for (int32_t i = 0; i < edgeCount; i++) {
   290             nsIAtom* curVertexAtom = data2->ObjectAt(i);
   291             nsAutoString curVertexStr;
   292             curVertexAtom->ToString(curVertexStr);
   293             nsCStringKey *curVertex = new nsCStringKey(ToNewCString(curVertexStr), 
   294                                         curVertexStr.Length(), nsCStringKey::OWN);
   296             BFSTableData *curVertexState = (BFSTableData*)lBFSTable.Get(curVertex);
   297             if (!curVertexState) {
   298                 delete curVertex;
   299                 return NS_ERROR_FAILURE;
   300             }
   302             if (white == curVertexState->color) {
   303                 curVertexState->color = gray;
   304                 curVertexState->distance = headVertexState->distance + 1;
   305                 curVertexState->predecessor = (nsCStringKey*)currentHead->Clone();
   306                 if (!curVertexState->predecessor) {
   307                     delete curVertex;
   308                     return NS_ERROR_OUT_OF_MEMORY;
   309                 }
   310                 grayQ.Push(curVertex);
   311             } else {
   312                 delete curVertex; // if this vertex has already been discovered, we don't want
   313                                   // to leak it. (non-discovered vertex's get cleaned up when
   314                                   // they're popped).
   315             }
   316         }
   317         headVertexState->color = black;
   318         nsCStringKey *cur = (nsCStringKey*)grayQ.PopFront();
   319         delete cur;
   320         cur = nullptr;
   321     }
   322     // The shortest path (if any) has been generated and is represented by the chain of
   323     // BFSTableData->predecessor keys. Start at the bottom and work our way up.
   325     // first parse out the FROM and TO MIME-types being registered.
   327     nsAutoCString fromStr, toStr;
   328     rv = ParseFromTo(aContractID, fromStr, toStr);
   329     if (NS_FAILED(rv)) return rv;
   331     // get the root CONTRACTID
   332     nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
   333     nsTArray<nsCString> *shortestPath = new nsTArray<nsCString>();
   335     nsCStringKey toMIMEType(toStr);
   336     data = (BFSTableData*)lBFSTable.Get(&toMIMEType);
   337     if (!data) {
   338         // If this vertex isn't in the BFSTable, then no-one has registered for it,
   339         // therefore we can't do the conversion.
   340         delete shortestPath;
   341         return NS_ERROR_FAILURE;
   342     }
   344     while (data) {
   345         nsCStringKey *key = data->key;
   347         if (fromStr.Equals(key->GetString())) {
   348             // found it. We're done here.
   349             *aEdgeList = shortestPath;
   350             return NS_OK;
   351         }
   353         // reconstruct the CONTRACTID.
   354         // Get the predecessor.
   355         if (!data->predecessor) break; // no predecessor
   356         BFSTableData *predecessorData = (BFSTableData*)lBFSTable.Get(data->predecessor);
   358         if (!predecessorData) break; // no predecessor, chain doesn't exist.
   360         // build out the CONTRACTID.
   361         nsAutoCString newContractID(ContractIDPrefix);
   362         newContractID.AppendLiteral("?from=");
   364         nsCStringKey *predecessorKey = predecessorData->key;
   365         newContractID.Append(predecessorKey->GetString());
   367         newContractID.AppendLiteral("&to=");
   368         newContractID.Append(key->GetString());
   370         // Add this CONTRACTID to the chain.
   371         rv = shortestPath->AppendElement(newContractID) ? NS_OK : NS_ERROR_FAILURE;  // XXX this method incorrectly returns a bool
   372         NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed");
   374         // move up the tree.
   375         data = predecessorData;
   376     }
   377     delete shortestPath;
   378     return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
   379 }
   382 /////////////////////////////////////////////////////
   383 // nsIStreamConverterService methods
   384 NS_IMETHODIMP
   385 nsStreamConverterService::CanConvert(const char* aFromType,
   386                                      const char* aToType,
   387                                      bool* _retval) {
   388     nsCOMPtr<nsIComponentRegistrar> reg;
   389     nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
   390     if (NS_FAILED(rv))
   391         return rv;
   393     nsAutoCString contractID;
   394     contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
   395     contractID.Append(aFromType);
   396     contractID.AppendLiteral("&to=");
   397     contractID.Append(aToType);
   399     // See if we have a direct match
   400     rv = reg->IsContractIDRegistered(contractID.get(), _retval);
   401     if (NS_FAILED(rv))
   402         return rv;
   403     if (*_retval)
   404         return NS_OK;
   406     // Otherwise try the graph.
   407     rv = BuildGraph();
   408     if (NS_FAILED(rv))
   409         return rv;
   411     nsTArray<nsCString> *converterChain = nullptr;
   412     rv = FindConverter(contractID.get(), &converterChain);
   413     *_retval = NS_SUCCEEDED(rv);
   415     delete converterChain;
   416     return NS_OK;
   417 }
   419 NS_IMETHODIMP
   420 nsStreamConverterService::Convert(nsIInputStream *aFromStream,
   421                                   const char *aFromType, 
   422                                   const char *aToType,
   423                                   nsISupports *aContext,
   424                                   nsIInputStream **_retval) {
   425     if (!aFromStream || !aFromType || !aToType || !_retval) return NS_ERROR_NULL_POINTER;
   426     nsresult rv;
   428     // first determine whether we can even handle this conversion
   429     // build a CONTRACTID
   430     nsAutoCString contractID;
   431     contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
   432     contractID.Append(aFromType);
   433     contractID.AppendLiteral("&to=");
   434     contractID.Append(aToType);
   435     const char *cContractID = contractID.get();
   437     nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
   438     if (NS_FAILED(rv)) {
   439         // couldn't go direct, let's try walking the graph of converters.
   440         rv = BuildGraph();
   441         if (NS_FAILED(rv)) return rv;
   443         nsTArray<nsCString> *converterChain = nullptr;
   445         rv = FindConverter(cContractID, &converterChain);
   446         if (NS_FAILED(rv)) {
   447             // can't make this conversion.
   448             // XXX should have a more descriptive error code.
   449             return NS_ERROR_FAILURE;
   450         }
   452         int32_t edgeCount = int32_t(converterChain->Length());
   453         NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
   456         // convert the stream using each edge of the graph as a step.
   457         // this is our stream conversion traversal.
   458         nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
   459         nsCOMPtr<nsIInputStream> convertedData;
   461         for (int32_t i = edgeCount-1; i >= 0; i--) {
   462             const char *lContractID = converterChain->ElementAt(i).get();
   464             converter = do_CreateInstance(lContractID, &rv);
   466             if (NS_FAILED(rv)) {
   467                 delete converterChain;
   468                 return rv;
   469             }
   471             nsAutoCString fromStr, toStr;
   472             rv = ParseFromTo(lContractID, fromStr, toStr);
   473             if (NS_FAILED(rv)) {
   474                 delete converterChain;
   475                 return rv;
   476             }
   478             rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(), aContext, getter_AddRefs(convertedData));
   479             dataToConvert = convertedData;
   480             if (NS_FAILED(rv)) {
   481                 delete converterChain;
   482                 return rv;
   483             }
   484         }
   486         delete converterChain;
   487         *_retval = convertedData;
   488         NS_ADDREF(*_retval);
   490     } else {
   491         // we're going direct.
   492         rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
   493     }
   495     return rv;
   496 }
   499 NS_IMETHODIMP
   500 nsStreamConverterService::AsyncConvertData(const char *aFromType, 
   501                                            const char *aToType, 
   502                                            nsIStreamListener *aListener,
   503                                            nsISupports *aContext,
   504                                            nsIStreamListener **_retval) {
   505     if (!aFromType || !aToType || !aListener || !_retval) return NS_ERROR_NULL_POINTER;
   507     nsresult rv;
   509     // first determine whether we can even handle this conversion
   510     // build a CONTRACTID
   511     nsAutoCString contractID;
   512     contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
   513     contractID.Append(aFromType);
   514     contractID.AppendLiteral("&to=");
   515     contractID.Append(aToType);
   516     const char *cContractID = contractID.get();
   518     nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
   519     if (NS_FAILED(rv)) {
   520         // couldn't go direct, let's try walking the graph of converters.
   521         rv = BuildGraph();
   522         if (NS_FAILED(rv)) return rv;
   524         nsTArray<nsCString> *converterChain = nullptr;
   526         rv = FindConverter(cContractID, &converterChain);
   527         if (NS_FAILED(rv)) {
   528             // can't make this conversion.
   529             // XXX should have a more descriptive error code.
   530             return NS_ERROR_FAILURE;
   531         }
   533         // aListener is the listener that wants the final, converted, data.
   534         // we initialize finalListener w/ aListener so it gets put at the 
   535         // tail end of the chain, which in the loop below, means the *first*
   536         // converter created.
   537         nsCOMPtr<nsIStreamListener> finalListener = aListener;
   539         // convert the stream using each edge of the graph as a step.
   540         // this is our stream conversion traversal.
   541         int32_t edgeCount = int32_t(converterChain->Length());
   542         NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
   543         for (int i = 0; i < edgeCount; i++) {
   544             const char *lContractID = converterChain->ElementAt(i).get();
   546             // create the converter for this from/to pair
   547             nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
   548             NS_ASSERTION(converter, "graph construction problem, built a contractid that wasn't registered");
   550             nsAutoCString fromStr, toStr;
   551             rv = ParseFromTo(lContractID, fromStr, toStr);
   552             if (NS_FAILED(rv)) {
   553                 delete converterChain;
   554                 return rv;
   555             }
   557             // connect the converter w/ the listener that should get the converted data.
   558             rv = converter->AsyncConvertData(fromStr.get(), toStr.get(), finalListener, aContext);
   559             if (NS_FAILED(rv)) {
   560                 delete converterChain;
   561                 return rv;
   562             }
   564             nsCOMPtr<nsIStreamListener> chainListener(do_QueryInterface(converter, &rv));
   565             if (NS_FAILED(rv)) {
   566                 delete converterChain;
   567                 return rv;
   568             }
   570             // the last iteration of this loop will result in finalListener
   571             // pointing to the converter that "starts" the conversion chain.
   572             // this converter's "from" type is the original "from" type. Prior
   573             // to the last iteration, finalListener will continuously be wedged
   574             // into the next listener in the chain, then be updated.
   575             finalListener = chainListener;
   576         }
   577         delete converterChain;
   578         // return the first listener in the chain.
   579         *_retval = finalListener;
   580         NS_ADDREF(*_retval);
   582     } else {
   583         // we're going direct.
   584         *_retval = listener;
   585         NS_ADDREF(*_retval);
   587         rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
   588     }
   590     return rv;
   592 }
   594 nsresult
   595 NS_NewStreamConv(nsStreamConverterService** aStreamConv)
   596 {
   597     NS_PRECONDITION(aStreamConv != nullptr, "null ptr");
   598     if (!aStreamConv) return NS_ERROR_NULL_POINTER;
   600     *aStreamConv = new nsStreamConverterService();
   601     NS_ADDREF(*aStreamConv);
   603     return NS_OK;
   604 }

mercurial