tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsStreamConverterService.cpp (18298B)


      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 #include "nsComponentManagerUtils.h"
      8 #include "nsStreamConverterService.h"
      9 #include "nsIComponentRegistrar.h"
     10 #include "nsString.h"
     11 #include "nsAtom.h"
     12 #include "nsDeque.h"
     13 #include "nsIInputStream.h"
     14 #include "nsIStreamConverter.h"
     15 #include "nsICategoryManager.h"
     16 #include "nsXPCOM.h"
     17 #include "nsISupportsPrimitives.h"
     18 #include "nsTArray.h"
     19 #include "nsServiceManagerUtils.h"
     20 #include "nsISimpleEnumerator.h"
     21 #include "mozilla/Components.h"
     22 #include "mozilla/UniquePtr.h"
     23 
     24 ///////////////////////////////////////////////////////////////////
     25 // Breadth-First-Search (BFS) algorithm state classes and types.
     26 
     27 // Used to establish discovered verticies.
     28 enum BFScolors { white, gray, black };
     29 
     30 // BFS hashtable data class.
     31 struct BFSTableData {
     32  nsCString key;
     33  BFScolors color;
     34  int32_t distance;
     35  mozilla::UniquePtr<nsCString> predecessor;
     36 
     37  explicit BFSTableData(const nsACString& aKey)
     38      : key(aKey), color(white), distance(-1) {}
     39 };
     40 
     41 ////////////////////////////////////////////////////////////
     42 // nsISupports methods
     43 NS_IMPL_ISUPPORTS(nsStreamConverterService, nsIStreamConverterService)
     44 
     45 ////////////////////////////////////////////////////////////
     46 // nsIStreamConverterService methods
     47 
     48 ////////////////////////////////////////////////////////////
     49 // nsStreamConverterService methods
     50 
     51 // Builds the graph represented as an adjacency list (and built up in
     52 // memory using an nsObjectHashtable and nsCOMArray combination).
     53 //
     54 // :BuildGraph() consults the category manager for all stream converter
     55 // CONTRACTIDS then fills the adjacency list with edges.
     56 // An edge in this case is comprised of a FROM and TO MIME type combination.
     57 //
     58 // CONTRACTID format:
     59 // @mozilla.org/streamconv;1?from=text/html&to=text/plain
     60 // XXX curently we only handle a single from and to combo, we should repeat the
     61 // XXX registration process for any series of from-to combos.
     62 // XXX can use nsTokenizer for this.
     63 //
     64 
     65 nsresult nsStreamConverterService::BuildGraph() {
     66  nsresult rv;
     67 
     68  nsCOMPtr<nsICategoryManager> catmgr(
     69      mozilla::components::CategoryManager::Service(&rv));
     70  if (NS_FAILED(rv)) return rv;
     71 
     72  nsCOMPtr<nsISimpleEnumerator> entries;
     73  rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY,
     74                                 getter_AddRefs(entries));
     75  if (NS_FAILED(rv)) return rv;
     76 
     77  // go through each entry to build the graph
     78  nsCOMPtr<nsISupports> supports;
     79  nsCOMPtr<nsISupportsCString> entry;
     80  rv = entries->GetNext(getter_AddRefs(supports));
     81  while (NS_SUCCEEDED(rv)) {
     82    entry = do_QueryInterface(supports);
     83 
     84    // get the entry string
     85    nsAutoCString entryString;
     86    rv = entry->GetData(entryString);
     87    if (NS_FAILED(rv)) return rv;
     88 
     89    // cobble the entry string w/ the converter key to produce a full
     90    // contractID.
     91    nsAutoCString contractID(NS_ISTREAMCONVERTER_KEY);
     92    contractID.Append(entryString);
     93 
     94    // now we've got the CONTRACTID, let's parse it up.
     95    rv = AddAdjacency(contractID.get());
     96    if (NS_FAILED(rv)) return rv;
     97 
     98    rv = entries->GetNext(getter_AddRefs(supports));
     99  }
    100 
    101  return NS_OK;
    102 }
    103 
    104 // XXX currently you can not add the same adjacency (i.e. you can't have
    105 // multiple
    106 // XXX stream converters registering to handle the same from-to combination.
    107 // It's
    108 // XXX not programatically prohibited, it's just that results are un-predictable
    109 // XXX right now.
    110 nsresult nsStreamConverterService::AddAdjacency(const char* aContractID) {
    111  nsresult rv;
    112  // first parse out the FROM and TO MIME-types.
    113 
    114  nsAutoCString fromStr, toStr;
    115  rv = ParseFromTo(aContractID, fromStr, toStr);
    116  if (NS_FAILED(rv)) return rv;
    117 
    118  // Each MIME-type is a vertex in the graph, so first lets make sure
    119  // each MIME-type is represented as a key in our hashtable.
    120 
    121  nsTArray<RefPtr<nsAtom>>* const fromEdges =
    122      mAdjacencyList.GetOrInsertNew(fromStr);
    123 
    124  mAdjacencyList.GetOrInsertNew(toStr);
    125 
    126  // Now we know the FROM and TO types are represented as keys in the hashtable.
    127  // Let's "connect" the verticies, making an edge.
    128 
    129  RefPtr<nsAtom> vertex = NS_Atomize(toStr);
    130  if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
    131 
    132  NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
    133  if (!fromEdges) return NS_ERROR_FAILURE;
    134 
    135  // XXX(Bug 1631371) Check if this should use a fallible operation as it
    136  // pretended earlier.
    137  fromEdges->AppendElement(vertex);
    138  return NS_OK;
    139 }
    140 
    141 nsresult nsStreamConverterService::ParseFromTo(const char* aContractID,
    142                                               nsCString& aFromRes,
    143                                               nsCString& aToRes) {
    144  nsAutoCString ContractIDStr(aContractID);
    145 
    146  int32_t fromLoc = ContractIDStr.Find("from=");
    147  int32_t toLoc = ContractIDStr.Find("to=");
    148  if (-1 == fromLoc || -1 == toLoc) return NS_ERROR_FAILURE;
    149 
    150  fromLoc = fromLoc + 5;
    151  toLoc = toLoc + 3;
    152 
    153  nsAutoCString fromStr, toStr;
    154 
    155  ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
    156  ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
    157 
    158  aFromRes.Assign(fromStr);
    159  aToRes.Assign(toStr);
    160 
    161  return NS_OK;
    162 }
    163 
    164 using BFSHashTable = nsClassHashtable<nsCStringHashKey, BFSTableData>;
    165 
    166 // nsObjectHashtable enumerator functions.
    167 
    168 class CStreamConvDeallocator : public nsDequeFunctor<nsCString> {
    169 public:
    170  void operator()(nsCString* anObject) override { delete anObject; }
    171 };
    172 
    173 // walks the graph using a breadth-first-search algorithm which generates a
    174 // discovered verticies tree. This tree is then walked up (from destination
    175 // vertex, to origin vertex) and each link in the chain is added to an
    176 // nsStringArray. A direct lookup for the given CONTRACTID should be made prior
    177 // to calling this method in an attempt to find a direct converter rather than
    178 // walking the graph.
    179 nsresult nsStreamConverterService::FindConverter(
    180    const char* aContractID, nsTArray<nsCString>** aEdgeList) {
    181  nsresult rv;
    182  if (!aEdgeList) return NS_ERROR_NULL_POINTER;
    183  *aEdgeList = nullptr;
    184 
    185  // walk the graph in search of the appropriate converter.
    186 
    187  uint32_t vertexCount = mAdjacencyList.Count();
    188  if (0 >= vertexCount) return NS_ERROR_FAILURE;
    189 
    190  // Create a corresponding color table for each vertex in the graph.
    191  BFSHashTable lBFSTable;
    192  for (const auto& entry : mAdjacencyList) {
    193    const nsACString& key = entry.GetKey();
    194    MOZ_ASSERT(entry.GetWeak(), "no data in the table iteration");
    195    lBFSTable.InsertOrUpdate(key, mozilla::MakeUnique<BFSTableData>(key));
    196  }
    197 
    198  NS_ASSERTION(lBFSTable.Count() == vertexCount,
    199               "strmconv BFS table init problem");
    200 
    201  // This is our source vertex; our starting point.
    202  nsAutoCString fromC, toC;
    203  rv = ParseFromTo(aContractID, fromC, toC);
    204  if (NS_FAILED(rv)) return rv;
    205 
    206  BFSTableData* data = lBFSTable.Get(fromC);
    207  if (!data) {
    208    return NS_ERROR_FAILURE;
    209  }
    210 
    211  data->color = gray;
    212  data->distance = 0;
    213  auto* dtorFunc = new CStreamConvDeallocator();
    214 
    215  nsDeque grayQ(dtorFunc);
    216 
    217  // Now generate the shortest path tree.
    218  grayQ.Push(new nsCString(fromC));
    219  while (0 < grayQ.GetSize()) {
    220    nsCString* currentHead = (nsCString*)grayQ.PeekFront();
    221    nsTArray<RefPtr<nsAtom>>* data2 = mAdjacencyList.Get(*currentHead);
    222    if (!data2) return NS_ERROR_FAILURE;
    223 
    224    // Get the state of the current head to calculate the distance of each
    225    // reachable vertex in the loop.
    226    BFSTableData* headVertexState = lBFSTable.Get(*currentHead);
    227    if (!headVertexState) return NS_ERROR_FAILURE;
    228 
    229    int32_t edgeCount = data2->Length();
    230 
    231    for (int32_t i = 0; i < edgeCount; i++) {
    232      nsAtom* curVertexAtom = data2->ElementAt(i);
    233      auto* curVertex = new nsCString();
    234      curVertexAtom->ToUTF8String(*curVertex);
    235 
    236      BFSTableData* curVertexState = lBFSTable.Get(*curVertex);
    237      if (!curVertexState) {
    238        delete curVertex;
    239        return NS_ERROR_FAILURE;
    240      }
    241 
    242      if (white == curVertexState->color) {
    243        curVertexState->color = gray;
    244        curVertexState->distance = headVertexState->distance + 1;
    245        curVertexState->predecessor =
    246            mozilla::MakeUnique<nsCString>(*currentHead);
    247        grayQ.Push(curVertex);
    248      } else {
    249        delete curVertex;  // if this vertex has already been discovered, we
    250                           // don't want to leak it. (non-discovered vertex's
    251                           // get cleaned up when they're popped).
    252      }
    253    }
    254    headVertexState->color = black;
    255    nsCString* cur = (nsCString*)grayQ.PopFront();
    256    delete cur;
    257    cur = nullptr;
    258  }
    259  // The shortest path (if any) has been generated and is represented by the
    260  // chain of BFSTableData->predecessor keys. Start at the bottom and work our
    261  // way up.
    262 
    263  // first parse out the FROM and TO MIME-types being registered.
    264 
    265  nsAutoCString fromStr, toMIMEType;
    266  rv = ParseFromTo(aContractID, fromStr, toMIMEType);
    267  if (NS_FAILED(rv)) return rv;
    268 
    269  // get the root CONTRACTID
    270  nsAutoCString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
    271  auto* shortestPath = new nsTArray<nsCString>();
    272 
    273  data = lBFSTable.Get(toMIMEType);
    274  if (!data) {
    275    // If this vertex isn't in the BFSTable, then no-one has registered for it,
    276    // therefore we can't do the conversion.
    277    delete shortestPath;
    278    return NS_ERROR_FAILURE;
    279  }
    280 
    281  while (data) {
    282    if (fromStr.Equals(data->key)) {
    283      // found it. We're done here.
    284      *aEdgeList = shortestPath;
    285      return NS_OK;
    286    }
    287 
    288    // reconstruct the CONTRACTID.
    289    // Get the predecessor.
    290    if (!data->predecessor) break;  // no predecessor
    291    BFSTableData* predecessorData = lBFSTable.Get(*data->predecessor);
    292 
    293    if (!predecessorData) break;  // no predecessor, chain doesn't exist.
    294 
    295    // build out the CONTRACTID.
    296    nsAutoCString newContractID(ContractIDPrefix);
    297    newContractID.AppendLiteral("?from=");
    298 
    299    newContractID.Append(predecessorData->key);
    300 
    301    newContractID.AppendLiteral("&to=");
    302    newContractID.Append(data->key);
    303 
    304    // Add this CONTRACTID to the chain.
    305    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    306    // pretended earlier.
    307    shortestPath->AppendElement(newContractID);
    308 
    309    // move up the tree.
    310    data = predecessorData;
    311  }
    312  delete shortestPath;
    313  return NS_ERROR_FAILURE;  // couldn't find a stream converter or chain.
    314 }
    315 
    316 /////////////////////////////////////////////////////
    317 // nsIStreamConverterService methods
    318 NS_IMETHODIMP
    319 nsStreamConverterService::CanConvert(const char* aFromType, const char* aToType,
    320                                     bool* _retval) {
    321  nsCOMPtr<nsIComponentRegistrar> reg;
    322  nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
    323  if (NS_FAILED(rv)) return rv;
    324 
    325  nsAutoCString contractID;
    326  contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
    327  contractID.Append(aFromType);
    328  contractID.AppendLiteral("&to=");
    329  contractID.Append(aToType);
    330 
    331  // See if we have a direct match
    332  rv = reg->IsContractIDRegistered(contractID.get(), _retval);
    333  if (NS_FAILED(rv)) return rv;
    334  if (*_retval) return NS_OK;
    335 
    336  // Otherwise try the graph.
    337  rv = BuildGraph();
    338  if (NS_FAILED(rv)) return rv;
    339 
    340  nsTArray<nsCString>* converterChain = nullptr;
    341  rv = FindConverter(contractID.get(), &converterChain);
    342  *_retval = NS_SUCCEEDED(rv);
    343 
    344  delete converterChain;
    345  return NS_OK;
    346 }
    347 
    348 NS_IMETHODIMP
    349 nsStreamConverterService::ConvertedType(const nsACString& aFromType,
    350                                        nsIChannel* aChannel,
    351                                        nsACString& aOutToType) {
    352  // first determine whether we can even handle this conversion
    353  // build a CONTRACTID
    354  nsAutoCString contractID;
    355  contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
    356  contractID.Append(aFromType);
    357  contractID.AppendLiteral("&to=*/*");
    358  const char* cContractID = contractID.get();
    359 
    360  nsresult rv;
    361  nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
    362  if (NS_SUCCEEDED(rv)) {
    363    return converter->GetConvertedType(aFromType, aChannel, aOutToType);
    364  }
    365  return rv;
    366 }
    367 
    368 NS_IMETHODIMP
    369 nsStreamConverterService::Convert(nsIInputStream* aFromStream,
    370                                  const char* aFromType, const char* aToType,
    371                                  nsISupports* aContext,
    372                                  nsIInputStream** _retval) {
    373  if (!aFromStream || !aFromType || !aToType || !_retval) {
    374    return NS_ERROR_NULL_POINTER;
    375  }
    376  nsresult rv;
    377 
    378  // first determine whether we can even handle this conversion
    379  // build a CONTRACTID
    380  nsAutoCString contractID;
    381  contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
    382  contractID.Append(aFromType);
    383  contractID.AppendLiteral("&to=");
    384  contractID.Append(aToType);
    385  const char* cContractID = contractID.get();
    386 
    387  nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
    388  if (NS_FAILED(rv)) {
    389    // couldn't go direct, let's try walking the graph of converters.
    390    rv = BuildGraph();
    391    if (NS_FAILED(rv)) return rv;
    392 
    393    nsTArray<nsCString>* converterChain = nullptr;
    394 
    395    rv = FindConverter(cContractID, &converterChain);
    396    if (NS_FAILED(rv)) {
    397      // can't make this conversion.
    398      // XXX should have a more descriptive error code.
    399      return NS_ERROR_FAILURE;
    400    }
    401 
    402    int32_t edgeCount = int32_t(converterChain->Length());
    403    NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
    404 
    405    // convert the stream using each edge of the graph as a step.
    406    // this is our stream conversion traversal.
    407    nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
    408    nsCOMPtr<nsIInputStream> convertedData;
    409 
    410    for (int32_t i = edgeCount - 1; i >= 0; i--) {
    411      const char* lContractID = converterChain->ElementAt(i).get();
    412 
    413      converter = do_CreateInstance(lContractID, &rv);
    414 
    415      if (NS_FAILED(rv)) {
    416        delete converterChain;
    417        return rv;
    418      }
    419 
    420      nsAutoCString fromStr, toStr;
    421      rv = ParseFromTo(lContractID, fromStr, toStr);
    422      if (NS_FAILED(rv)) {
    423        delete converterChain;
    424        return rv;
    425      }
    426 
    427      rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(),
    428                              aContext, getter_AddRefs(convertedData));
    429      dataToConvert = convertedData;
    430      if (NS_FAILED(rv)) {
    431        delete converterChain;
    432        return rv;
    433      }
    434    }
    435 
    436    delete converterChain;
    437    convertedData.forget(_retval);
    438  } else {
    439    // we're going direct.
    440    rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
    441  }
    442 
    443  return rv;
    444 }
    445 
    446 NS_IMETHODIMP
    447 nsStreamConverterService::AsyncConvertData(const char* aFromType,
    448                                           const char* aToType,
    449                                           nsIStreamListener* aListener,
    450                                           nsISupports* aContext,
    451                                           nsIStreamListener** _retval) {
    452  if (!aFromType || !aToType || !aListener || !_retval) {
    453    return NS_ERROR_NULL_POINTER;
    454  }
    455 
    456  nsresult rv;
    457 
    458  // first determine whether we can even handle this conversion
    459  // build a CONTRACTID
    460  nsAutoCString contractID;
    461  contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
    462  contractID.Append(aFromType);
    463  contractID.AppendLiteral("&to=");
    464  contractID.Append(aToType);
    465  const char* cContractID = contractID.get();
    466 
    467  nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
    468  if (NS_FAILED(rv)) {
    469    // couldn't go direct, let's try walking the graph of converters.
    470    rv = BuildGraph();
    471    if (NS_FAILED(rv)) return rv;
    472 
    473    nsTArray<nsCString>* converterChain = nullptr;
    474 
    475    rv = FindConverter(cContractID, &converterChain);
    476    if (NS_FAILED(rv)) {
    477      // can't make this conversion.
    478      // XXX should have a more descriptive error code.
    479      return NS_ERROR_FAILURE;
    480    }
    481 
    482    // aListener is the listener that wants the final, converted, data.
    483    // we initialize finalListener w/ aListener so it gets put at the
    484    // tail end of the chain, which in the loop below, means the *first*
    485    // converter created.
    486    nsCOMPtr<nsIStreamListener> finalListener = aListener;
    487 
    488    // convert the stream using each edge of the graph as a step.
    489    // this is our stream conversion traversal.
    490    int32_t edgeCount = int32_t(converterChain->Length());
    491    NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
    492    for (int i = 0; i < edgeCount; i++) {
    493      const char* lContractID = converterChain->ElementAt(i).get();
    494 
    495      // create the converter for this from/to pair
    496      nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
    497      NS_ASSERTION(converter,
    498                   "graph construction problem, built a contractid that wasn't "
    499                   "registered");
    500 
    501      nsAutoCString fromStr, toStr;
    502      rv = ParseFromTo(lContractID, fromStr, toStr);
    503      if (NS_FAILED(rv)) {
    504        delete converterChain;
    505        return rv;
    506      }
    507 
    508      // connect the converter w/ the listener that should get the converted
    509      // data.
    510      rv = converter->AsyncConvertData(fromStr.get(), toStr.get(),
    511                                       finalListener, aContext);
    512      if (NS_FAILED(rv)) {
    513        delete converterChain;
    514        return rv;
    515      }
    516 
    517      // the last iteration of this loop will result in finalListener
    518      // pointing to the converter that "starts" the conversion chain.
    519      // this converter's "from" type is the original "from" type. Prior
    520      // to the last iteration, finalListener will continuously be wedged
    521      // into the next listener in the chain, then be updated.
    522      finalListener = converter;
    523    }
    524    delete converterChain;
    525    // return the first listener in the chain.
    526    finalListener.forget(_retval);
    527  } else {
    528    // we're going direct.
    529    rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
    530    listener.forget(_retval);
    531  }
    532 
    533  return rv;
    534 }
    535 
    536 nsresult NS_NewStreamConv(nsStreamConverterService** aStreamConv) {
    537  MOZ_ASSERT(aStreamConv != nullptr, "null ptr");
    538  if (!aStreamConv) return NS_ERROR_NULL_POINTER;
    539 
    540  RefPtr<nsStreamConverterService> conv = new nsStreamConverterService();
    541  conv.forget(aStreamConv);
    542 
    543  return NS_OK;
    544 }