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 "nsCOMArray.h"
19 #include "nsTArray.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsISimpleEnumerator.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 
BFSTableDataBFSTableData37   explicit BFSTableData(const nsACString& aKey)
38       : key(aKey), color(white), distance(-1) {}
39 };
40 
41 ////////////////////////////////////////////////////////////
42 // nsISupports methods
NS_IMPL_ISUPPORTS(nsStreamConverterService,nsIStreamConverterService)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       do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &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.
AddAdjacency(const char * aContractID)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 
ParseFromTo(const char * aContractID,nsCString & aFromRes,nsCString & aToRes)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:
operator ()(nsCString * anObject)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.
FindConverter(const char * aContractID,nsTArray<nsCString> ** aEdgeList)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
CanConvert(const char * aFromType,const char * aToType,bool * _retval)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
ConvertedType(const nsACString & aFromType,nsIChannel * aChannel,nsACString & aOutToType)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
Convert(nsIInputStream * aFromStream,const char * aFromType,const char * aToType,nsISupports * aContext,nsIInputStream ** _retval)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
AsyncConvertData(const char * aFromType,const char * aToType,nsIStreamListener * aListener,nsISupports * aContext,nsIStreamListener ** _retval)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 
NS_NewStreamConv(nsStreamConverterService ** aStreamConv)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 }
545