1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "nsComponentManagerUtils.h"
8 #include "nsStreamConverterService.h"
9 #include "nsIComponentRegistrar.h"
13 #include "nsIInputStream.h"
14 #include "nsIStreamConverter.h"
15 #include "nsICategoryManager.h"
17 #include "nsISupportsPrimitives.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsISimpleEnumerator.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/UniquePtr.h"
24 ///////////////////////////////////////////////////////////////////
25 // Breadth-First-Search (BFS) algorithm state classes and types.
27 // Used to establish discovered verticies.
28 enum BFScolors
{ white
, gray
, black
};
30 // BFS hashtable data class.
35 mozilla::UniquePtr
<nsCString
> predecessor
;
37 explicit BFSTableData(const nsACString
& aKey
)
38 : key(aKey
), color(white
), distance(-1) {}
41 ////////////////////////////////////////////////////////////
42 // nsISupports methods
43 NS_IMPL_ISUPPORTS(nsStreamConverterService
, nsIStreamConverterService
)
45 ////////////////////////////////////////////////////////////
46 // nsIStreamConverterService methods
48 ////////////////////////////////////////////////////////////
49 // nsStreamConverterService methods
51 // Builds the graph represented as an adjacency list (and built up in
52 // memory using an nsObjectHashtable and nsCOMArray combination).
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.
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.
65 nsresult
nsStreamConverterService::BuildGraph() {
68 nsCOMPtr
<nsICategoryManager
> catmgr(
69 mozilla::components::CategoryManager::Service(&rv
));
70 if (NS_FAILED(rv
)) return rv
;
72 nsCOMPtr
<nsISimpleEnumerator
> entries
;
73 rv
= catmgr
->EnumerateCategory(NS_ISTREAMCONVERTER_KEY
,
74 getter_AddRefs(entries
));
75 if (NS_FAILED(rv
)) return rv
;
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
);
84 // get the entry string
85 nsAutoCString entryString
;
86 rv
= entry
->GetData(entryString
);
87 if (NS_FAILED(rv
)) return rv
;
89 // cobble the entry string w/ the converter key to produce a full
91 nsAutoCString
contractID(NS_ISTREAMCONVERTER_KEY
);
92 contractID
.Append(entryString
);
94 // now we've got the CONTRACTID, let's parse it up.
95 rv
= AddAdjacency(contractID
.get());
96 if (NS_FAILED(rv
)) return rv
;
98 rv
= entries
->GetNext(getter_AddRefs(supports
));
104 // XXX currently you can not add the same adjacency (i.e. you can't have
106 // XXX stream converters registering to handle the same from-to combination.
108 // XXX not programatically prohibited, it's just that results are un-predictable
110 nsresult
nsStreamConverterService::AddAdjacency(const char* aContractID
) {
112 // first parse out the FROM and TO MIME-types.
114 nsAutoCString fromStr
, toStr
;
115 rv
= ParseFromTo(aContractID
, fromStr
, toStr
);
116 if (NS_FAILED(rv
)) return rv
;
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.
121 nsTArray
<RefPtr
<nsAtom
>>* const fromEdges
=
122 mAdjacencyList
.GetOrInsertNew(fromStr
);
124 mAdjacencyList
.GetOrInsertNew(toStr
);
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.
129 RefPtr
<nsAtom
> vertex
= NS_Atomize(toStr
);
130 if (!vertex
) return NS_ERROR_OUT_OF_MEMORY
;
132 NS_ASSERTION(fromEdges
, "something wrong in adjacency list construction");
133 if (!fromEdges
) return NS_ERROR_FAILURE
;
135 // XXX(Bug 1631371) Check if this should use a fallible operation as it
136 // pretended earlier.
137 fromEdges
->AppendElement(vertex
);
141 nsresult
nsStreamConverterService::ParseFromTo(const char* aContractID
,
144 nsAutoCString
ContractIDStr(aContractID
);
146 int32_t fromLoc
= ContractIDStr
.Find("from=");
147 int32_t toLoc
= ContractIDStr
.Find("to=");
148 if (-1 == fromLoc
|| -1 == toLoc
) return NS_ERROR_FAILURE
;
150 fromLoc
= fromLoc
+ 5;
153 nsAutoCString fromStr
, toStr
;
155 ContractIDStr
.Mid(fromStr
, fromLoc
, toLoc
- 4 - fromLoc
);
156 ContractIDStr
.Mid(toStr
, toLoc
, ContractIDStr
.Length() - toLoc
);
158 aFromRes
.Assign(fromStr
);
159 aToRes
.Assign(toStr
);
164 using BFSHashTable
= nsClassHashtable
<nsCStringHashKey
, BFSTableData
>;
166 // nsObjectHashtable enumerator functions.
168 class CStreamConvDeallocator
: public nsDequeFunctor
<nsCString
> {
170 void operator()(nsCString
* anObject
) override
{ delete anObject
; }
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
) {
182 if (!aEdgeList
) return NS_ERROR_NULL_POINTER
;
183 *aEdgeList
= nullptr;
185 // walk the graph in search of the appropriate converter.
187 uint32_t vertexCount
= mAdjacencyList
.Count();
188 if (0 >= vertexCount
) return NS_ERROR_FAILURE
;
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
));
198 NS_ASSERTION(lBFSTable
.Count() == vertexCount
,
199 "strmconv BFS table init problem");
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
;
206 BFSTableData
* data
= lBFSTable
.Get(fromC
);
208 return NS_ERROR_FAILURE
;
213 auto* dtorFunc
= new CStreamConvDeallocator();
215 nsDeque
grayQ(dtorFunc
);
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
;
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
;
229 int32_t edgeCount
= data2
->Length();
231 for (int32_t i
= 0; i
< edgeCount
; i
++) {
232 nsAtom
* curVertexAtom
= data2
->ElementAt(i
);
233 auto* curVertex
= new nsCString();
234 curVertexAtom
->ToUTF8String(*curVertex
);
236 BFSTableData
* curVertexState
= lBFSTable
.Get(*curVertex
);
237 if (!curVertexState
) {
239 return NS_ERROR_FAILURE
;
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
);
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).
254 headVertexState
->color
= black
;
255 nsCString
* cur
= (nsCString
*)grayQ
.PopFront();
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
263 // first parse out the FROM and TO MIME-types being registered.
265 nsAutoCString fromStr
, toMIMEType
;
266 rv
= ParseFromTo(aContractID
, fromStr
, toMIMEType
);
267 if (NS_FAILED(rv
)) return rv
;
269 // get the root CONTRACTID
270 nsAutoCString
ContractIDPrefix(NS_ISTREAMCONVERTER_KEY
);
271 auto* shortestPath
= new nsTArray
<nsCString
>();
273 data
= lBFSTable
.Get(toMIMEType
);
275 // If this vertex isn't in the BFSTable, then no-one has registered for it,
276 // therefore we can't do the conversion.
278 return NS_ERROR_FAILURE
;
282 if (fromStr
.Equals(data
->key
)) {
283 // found it. We're done here.
284 *aEdgeList
= shortestPath
;
288 // reconstruct the CONTRACTID.
289 // Get the predecessor.
290 if (!data
->predecessor
) break; // no predecessor
291 BFSTableData
* predecessorData
= lBFSTable
.Get(*data
->predecessor
);
293 if (!predecessorData
) break; // no predecessor, chain doesn't exist.
295 // build out the CONTRACTID.
296 nsAutoCString
newContractID(ContractIDPrefix
);
297 newContractID
.AppendLiteral("?from=");
299 newContractID
.Append(predecessorData
->key
);
301 newContractID
.AppendLiteral("&to=");
302 newContractID
.Append(data
->key
);
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
);
310 data
= predecessorData
;
313 return NS_ERROR_FAILURE
; // couldn't find a stream converter or chain.
316 /////////////////////////////////////////////////////
317 // nsIStreamConverterService methods
319 nsStreamConverterService::CanConvert(const char* aFromType
, const char* aToType
,
321 nsCOMPtr
<nsIComponentRegistrar
> reg
;
322 nsresult rv
= NS_GetComponentRegistrar(getter_AddRefs(reg
));
323 if (NS_FAILED(rv
)) return rv
;
325 nsAutoCString contractID
;
326 contractID
.AssignLiteral(NS_ISTREAMCONVERTER_KEY
"?from=");
327 contractID
.Append(aFromType
);
328 contractID
.AppendLiteral("&to=");
329 contractID
.Append(aToType
);
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
;
336 // Otherwise try the graph.
338 if (NS_FAILED(rv
)) return rv
;
340 nsTArray
<nsCString
>* converterChain
= nullptr;
341 rv
= FindConverter(contractID
.get(), &converterChain
);
342 *_retval
= NS_SUCCEEDED(rv
);
344 delete converterChain
;
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();
361 nsCOMPtr
<nsIStreamConverter
> converter(do_CreateInstance(cContractID
, &rv
));
362 if (NS_SUCCEEDED(rv
)) {
363 return converter
->GetConvertedType(aFromType
, aChannel
, aOutToType
);
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
;
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();
387 nsCOMPtr
<nsIStreamConverter
> converter(do_CreateInstance(cContractID
, &rv
));
389 // couldn't go direct, let's try walking the graph of converters.
391 if (NS_FAILED(rv
)) return rv
;
393 nsTArray
<nsCString
>* converterChain
= nullptr;
395 rv
= FindConverter(cContractID
, &converterChain
);
397 // can't make this conversion.
398 // XXX should have a more descriptive error code.
399 return NS_ERROR_FAILURE
;
402 int32_t edgeCount
= int32_t(converterChain
->Length());
403 NS_ASSERTION(edgeCount
> 0, "findConverter should have failed");
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
;
410 for (int32_t i
= edgeCount
- 1; i
>= 0; i
--) {
411 const char* lContractID
= converterChain
->ElementAt(i
).get();
413 converter
= do_CreateInstance(lContractID
, &rv
);
416 delete converterChain
;
420 nsAutoCString fromStr
, toStr
;
421 rv
= ParseFromTo(lContractID
, fromStr
, toStr
);
423 delete converterChain
;
427 rv
= converter
->Convert(dataToConvert
, fromStr
.get(), toStr
.get(),
428 aContext
, getter_AddRefs(convertedData
));
429 dataToConvert
= convertedData
;
431 delete converterChain
;
436 delete converterChain
;
437 convertedData
.forget(_retval
);
439 // we're going direct.
440 rv
= converter
->Convert(aFromStream
, aFromType
, aToType
, aContext
, _retval
);
447 nsStreamConverterService::AsyncConvertData(const char* aFromType
,
449 nsIStreamListener
* aListener
,
450 nsISupports
* aContext
,
451 nsIStreamListener
** _retval
) {
452 if (!aFromType
|| !aToType
|| !aListener
|| !_retval
) {
453 return NS_ERROR_NULL_POINTER
;
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();
467 nsCOMPtr
<nsIStreamConverter
> listener(do_CreateInstance(cContractID
, &rv
));
469 // couldn't go direct, let's try walking the graph of converters.
471 if (NS_FAILED(rv
)) return rv
;
473 nsTArray
<nsCString
>* converterChain
= nullptr;
475 rv
= FindConverter(cContractID
, &converterChain
);
477 // can't make this conversion.
478 // XXX should have a more descriptive error code.
479 return NS_ERROR_FAILURE
;
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
;
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();
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 "
501 nsAutoCString fromStr
, toStr
;
502 rv
= ParseFromTo(lContractID
, fromStr
, toStr
);
504 delete converterChain
;
508 // connect the converter w/ the listener that should get the converted
510 rv
= converter
->AsyncConvertData(fromStr
.get(), toStr
.get(),
511 finalListener
, aContext
);
513 delete converterChain
;
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
;
524 delete converterChain
;
525 // return the first listener in the chain.
526 finalListener
.forget(_retval
);
528 // we're going direct.
529 rv
= listener
->AsyncConvertData(aFromType
, aToType
, aListener
, aContext
);
530 listener
.forget(_retval
);
536 nsresult
NS_NewStreamConv(nsStreamConverterService
** aStreamConv
) {
537 MOZ_ASSERT(aStreamConv
!= nullptr, "null ptr");
538 if (!aStreamConv
) return NS_ERROR_NULL_POINTER
;
540 RefPtr
<nsStreamConverterService
> conv
= new nsStreamConverterService();
541 conv
.forget(aStreamConv
);