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/UniquePtr.h"
23 ///////////////////////////////////////////////////////////////////
24 // Breadth-First-Search (BFS) algorithm state classes and types.
26 // Used to establish discovered verticies.
27 enum BFScolors
{ white
, gray
, black
};
29 // BFS hashtable data class.
34 mozilla::UniquePtr
<nsCString
> predecessor
;
36 explicit BFSTableData(const nsACString
& aKey
)
37 : key(aKey
), color(white
), distance(-1) {}
40 ////////////////////////////////////////////////////////////
41 // nsISupports methods
42 NS_IMPL_ISUPPORTS(nsStreamConverterService
, nsIStreamConverterService
)
44 ////////////////////////////////////////////////////////////
45 // nsIStreamConverterService methods
47 ////////////////////////////////////////////////////////////
48 // nsStreamConverterService methods
50 // Builds the graph represented as an adjacency list (and built up in
51 // memory using an nsObjectHashtable and nsCOMArray combination).
53 // :BuildGraph() consults the category manager for all stream converter
54 // CONTRACTIDS then fills the adjacency list with edges.
55 // An edge in this case is comprised of a FROM and TO MIME type combination.
58 // @mozilla.org/streamconv;1?from=text/html&to=text/plain
59 // XXX curently we only handle a single from and to combo, we should repeat the
60 // XXX registration process for any series of from-to combos.
61 // XXX can use nsTokenizer for this.
64 nsresult
nsStreamConverterService::BuildGraph() {
67 nsCOMPtr
<nsICategoryManager
> catmgr(
68 do_GetService(NS_CATEGORYMANAGER_CONTRACTID
, &rv
));
69 if (NS_FAILED(rv
)) return rv
;
71 nsCOMPtr
<nsISimpleEnumerator
> entries
;
72 rv
= catmgr
->EnumerateCategory(NS_ISTREAMCONVERTER_KEY
,
73 getter_AddRefs(entries
));
74 if (NS_FAILED(rv
)) return rv
;
76 // go through each entry to build the graph
77 nsCOMPtr
<nsISupports
> supports
;
78 nsCOMPtr
<nsISupportsCString
> entry
;
79 rv
= entries
->GetNext(getter_AddRefs(supports
));
80 while (NS_SUCCEEDED(rv
)) {
81 entry
= do_QueryInterface(supports
);
83 // get the entry string
84 nsAutoCString entryString
;
85 rv
= entry
->GetData(entryString
);
86 if (NS_FAILED(rv
)) return rv
;
88 // cobble the entry string w/ the converter key to produce a full
90 nsAutoCString
contractID(NS_ISTREAMCONVERTER_KEY
);
91 contractID
.Append(entryString
);
93 // now we've got the CONTRACTID, let's parse it up.
94 rv
= AddAdjacency(contractID
.get());
95 if (NS_FAILED(rv
)) return rv
;
97 rv
= entries
->GetNext(getter_AddRefs(supports
));
103 // XXX currently you can not add the same adjacency (i.e. you can't have
105 // XXX stream converters registering to handle the same from-to combination.
107 // XXX not programatically prohibited, it's just that results are un-predictable
109 nsresult
nsStreamConverterService::AddAdjacency(const char* aContractID
) {
111 // first parse out the FROM and TO MIME-types.
113 nsAutoCString fromStr
, toStr
;
114 rv
= ParseFromTo(aContractID
, fromStr
, toStr
);
115 if (NS_FAILED(rv
)) return rv
;
117 // Each MIME-type is a vertex in the graph, so first lets make sure
118 // each MIME-type is represented as a key in our hashtable.
120 nsTArray
<RefPtr
<nsAtom
>>* const fromEdges
=
121 mAdjacencyList
.GetOrInsertNew(fromStr
);
123 mAdjacencyList
.GetOrInsertNew(toStr
);
125 // Now we know the FROM and TO types are represented as keys in the hashtable.
126 // Let's "connect" the verticies, making an edge.
128 RefPtr
<nsAtom
> vertex
= NS_Atomize(toStr
);
129 if (!vertex
) return NS_ERROR_OUT_OF_MEMORY
;
131 NS_ASSERTION(fromEdges
, "something wrong in adjacency list construction");
132 if (!fromEdges
) return NS_ERROR_FAILURE
;
134 // XXX(Bug 1631371) Check if this should use a fallible operation as it
135 // pretended earlier.
136 fromEdges
->AppendElement(vertex
);
140 nsresult
nsStreamConverterService::ParseFromTo(const char* aContractID
,
143 nsAutoCString
ContractIDStr(aContractID
);
145 int32_t fromLoc
= ContractIDStr
.Find("from=");
146 int32_t toLoc
= ContractIDStr
.Find("to=");
147 if (-1 == fromLoc
|| -1 == toLoc
) return NS_ERROR_FAILURE
;
149 fromLoc
= fromLoc
+ 5;
152 nsAutoCString fromStr
, toStr
;
154 ContractIDStr
.Mid(fromStr
, fromLoc
, toLoc
- 4 - fromLoc
);
155 ContractIDStr
.Mid(toStr
, toLoc
, ContractIDStr
.Length() - toLoc
);
157 aFromRes
.Assign(fromStr
);
158 aToRes
.Assign(toStr
);
163 using BFSHashTable
= nsClassHashtable
<nsCStringHashKey
, BFSTableData
>;
165 // nsObjectHashtable enumerator functions.
167 class CStreamConvDeallocator
: public nsDequeFunctor
<nsCString
> {
169 void operator()(nsCString
* anObject
) override
{ delete anObject
; }
172 // walks the graph using a breadth-first-search algorithm which generates a
173 // discovered verticies tree. This tree is then walked up (from destination
174 // vertex, to origin vertex) and each link in the chain is added to an
175 // nsStringArray. A direct lookup for the given CONTRACTID should be made prior
176 // to calling this method in an attempt to find a direct converter rather than
177 // walking the graph.
178 nsresult
nsStreamConverterService::FindConverter(
179 const char* aContractID
, nsTArray
<nsCString
>** aEdgeList
) {
181 if (!aEdgeList
) return NS_ERROR_NULL_POINTER
;
182 *aEdgeList
= nullptr;
184 // walk the graph in search of the appropriate converter.
186 uint32_t vertexCount
= mAdjacencyList
.Count();
187 if (0 >= vertexCount
) return NS_ERROR_FAILURE
;
189 // Create a corresponding color table for each vertex in the graph.
190 BFSHashTable lBFSTable
;
191 for (const auto& entry
: mAdjacencyList
) {
192 const nsACString
& key
= entry
.GetKey();
193 MOZ_ASSERT(entry
.GetWeak(), "no data in the table iteration");
194 lBFSTable
.InsertOrUpdate(key
, mozilla::MakeUnique
<BFSTableData
>(key
));
197 NS_ASSERTION(lBFSTable
.Count() == vertexCount
,
198 "strmconv BFS table init problem");
200 // This is our source vertex; our starting point.
201 nsAutoCString fromC
, toC
;
202 rv
= ParseFromTo(aContractID
, fromC
, toC
);
203 if (NS_FAILED(rv
)) return rv
;
205 BFSTableData
* data
= lBFSTable
.Get(fromC
);
207 return NS_ERROR_FAILURE
;
212 auto* dtorFunc
= new CStreamConvDeallocator();
214 nsDeque
grayQ(dtorFunc
);
216 // Now generate the shortest path tree.
217 grayQ
.Push(new nsCString(fromC
));
218 while (0 < grayQ
.GetSize()) {
219 nsCString
* currentHead
= (nsCString
*)grayQ
.PeekFront();
220 nsTArray
<RefPtr
<nsAtom
>>* data2
= mAdjacencyList
.Get(*currentHead
);
221 if (!data2
) return NS_ERROR_FAILURE
;
223 // Get the state of the current head to calculate the distance of each
224 // reachable vertex in the loop.
225 BFSTableData
* headVertexState
= lBFSTable
.Get(*currentHead
);
226 if (!headVertexState
) return NS_ERROR_FAILURE
;
228 int32_t edgeCount
= data2
->Length();
230 for (int32_t i
= 0; i
< edgeCount
; i
++) {
231 nsAtom
* curVertexAtom
= data2
->ElementAt(i
);
232 auto* curVertex
= new nsCString();
233 curVertexAtom
->ToUTF8String(*curVertex
);
235 BFSTableData
* curVertexState
= lBFSTable
.Get(*curVertex
);
236 if (!curVertexState
) {
238 return NS_ERROR_FAILURE
;
241 if (white
== curVertexState
->color
) {
242 curVertexState
->color
= gray
;
243 curVertexState
->distance
= headVertexState
->distance
+ 1;
244 curVertexState
->predecessor
=
245 mozilla::MakeUnique
<nsCString
>(*currentHead
);
246 grayQ
.Push(curVertex
);
248 delete curVertex
; // if this vertex has already been discovered, we
249 // don't want to leak it. (non-discovered vertex's
250 // get cleaned up when they're popped).
253 headVertexState
->color
= black
;
254 nsCString
* cur
= (nsCString
*)grayQ
.PopFront();
258 // The shortest path (if any) has been generated and is represented by the
259 // chain of BFSTableData->predecessor keys. Start at the bottom and work our
262 // first parse out the FROM and TO MIME-types being registered.
264 nsAutoCString fromStr
, toMIMEType
;
265 rv
= ParseFromTo(aContractID
, fromStr
, toMIMEType
);
266 if (NS_FAILED(rv
)) return rv
;
268 // get the root CONTRACTID
269 nsAutoCString
ContractIDPrefix(NS_ISTREAMCONVERTER_KEY
);
270 auto* shortestPath
= new nsTArray
<nsCString
>();
272 data
= lBFSTable
.Get(toMIMEType
);
274 // If this vertex isn't in the BFSTable, then no-one has registered for it,
275 // therefore we can't do the conversion.
277 return NS_ERROR_FAILURE
;
281 if (fromStr
.Equals(data
->key
)) {
282 // found it. We're done here.
283 *aEdgeList
= shortestPath
;
287 // reconstruct the CONTRACTID.
288 // Get the predecessor.
289 if (!data
->predecessor
) break; // no predecessor
290 BFSTableData
* predecessorData
= lBFSTable
.Get(*data
->predecessor
);
292 if (!predecessorData
) break; // no predecessor, chain doesn't exist.
294 // build out the CONTRACTID.
295 nsAutoCString
newContractID(ContractIDPrefix
);
296 newContractID
.AppendLiteral("?from=");
298 newContractID
.Append(predecessorData
->key
);
300 newContractID
.AppendLiteral("&to=");
301 newContractID
.Append(data
->key
);
303 // Add this CONTRACTID to the chain.
304 // XXX(Bug 1631371) Check if this should use a fallible operation as it
305 // pretended earlier.
306 shortestPath
->AppendElement(newContractID
);
309 data
= predecessorData
;
312 return NS_ERROR_FAILURE
; // couldn't find a stream converter or chain.
315 /////////////////////////////////////////////////////
316 // nsIStreamConverterService methods
318 nsStreamConverterService::CanConvert(const char* aFromType
, const char* aToType
,
320 nsCOMPtr
<nsIComponentRegistrar
> reg
;
321 nsresult rv
= NS_GetComponentRegistrar(getter_AddRefs(reg
));
322 if (NS_FAILED(rv
)) return rv
;
324 nsAutoCString contractID
;
325 contractID
.AssignLiteral(NS_ISTREAMCONVERTER_KEY
"?from=");
326 contractID
.Append(aFromType
);
327 contractID
.AppendLiteral("&to=");
328 contractID
.Append(aToType
);
330 // See if we have a direct match
331 rv
= reg
->IsContractIDRegistered(contractID
.get(), _retval
);
332 if (NS_FAILED(rv
)) return rv
;
333 if (*_retval
) return NS_OK
;
335 // Otherwise try the graph.
337 if (NS_FAILED(rv
)) return rv
;
339 nsTArray
<nsCString
>* converterChain
= nullptr;
340 rv
= FindConverter(contractID
.get(), &converterChain
);
341 *_retval
= NS_SUCCEEDED(rv
);
343 delete converterChain
;
348 nsStreamConverterService::ConvertedType(const nsACString
& aFromType
,
349 nsIChannel
* aChannel
,
350 nsACString
& aOutToType
) {
351 // first determine whether we can even handle this conversion
352 // build a CONTRACTID
353 nsAutoCString contractID
;
354 contractID
.AssignLiteral(NS_ISTREAMCONVERTER_KEY
"?from=");
355 contractID
.Append(aFromType
);
356 contractID
.AppendLiteral("&to=*/*");
357 const char* cContractID
= contractID
.get();
360 nsCOMPtr
<nsIStreamConverter
> converter(do_CreateInstance(cContractID
, &rv
));
361 if (NS_SUCCEEDED(rv
)) {
362 return converter
->GetConvertedType(aFromType
, aChannel
, aOutToType
);
368 nsStreamConverterService::Convert(nsIInputStream
* aFromStream
,
369 const char* aFromType
, const char* aToType
,
370 nsISupports
* aContext
,
371 nsIInputStream
** _retval
) {
372 if (!aFromStream
|| !aFromType
|| !aToType
|| !_retval
) {
373 return NS_ERROR_NULL_POINTER
;
377 // first determine whether we can even handle this conversion
378 // build a CONTRACTID
379 nsAutoCString contractID
;
380 contractID
.AssignLiteral(NS_ISTREAMCONVERTER_KEY
"?from=");
381 contractID
.Append(aFromType
);
382 contractID
.AppendLiteral("&to=");
383 contractID
.Append(aToType
);
384 const char* cContractID
= contractID
.get();
386 nsCOMPtr
<nsIStreamConverter
> converter(do_CreateInstance(cContractID
, &rv
));
388 // couldn't go direct, let's try walking the graph of converters.
390 if (NS_FAILED(rv
)) return rv
;
392 nsTArray
<nsCString
>* converterChain
= nullptr;
394 rv
= FindConverter(cContractID
, &converterChain
);
396 // can't make this conversion.
397 // XXX should have a more descriptive error code.
398 return NS_ERROR_FAILURE
;
401 int32_t edgeCount
= int32_t(converterChain
->Length());
402 NS_ASSERTION(edgeCount
> 0, "findConverter should have failed");
404 // convert the stream using each edge of the graph as a step.
405 // this is our stream conversion traversal.
406 nsCOMPtr
<nsIInputStream
> dataToConvert
= aFromStream
;
407 nsCOMPtr
<nsIInputStream
> convertedData
;
409 for (int32_t i
= edgeCount
- 1; i
>= 0; i
--) {
410 const char* lContractID
= converterChain
->ElementAt(i
).get();
412 converter
= do_CreateInstance(lContractID
, &rv
);
415 delete converterChain
;
419 nsAutoCString fromStr
, toStr
;
420 rv
= ParseFromTo(lContractID
, fromStr
, toStr
);
422 delete converterChain
;
426 rv
= converter
->Convert(dataToConvert
, fromStr
.get(), toStr
.get(),
427 aContext
, getter_AddRefs(convertedData
));
428 dataToConvert
= convertedData
;
430 delete converterChain
;
435 delete converterChain
;
436 convertedData
.forget(_retval
);
438 // we're going direct.
439 rv
= converter
->Convert(aFromStream
, aFromType
, aToType
, aContext
, _retval
);
446 nsStreamConverterService::AsyncConvertData(const char* aFromType
,
448 nsIStreamListener
* aListener
,
449 nsISupports
* aContext
,
450 nsIStreamListener
** _retval
) {
451 if (!aFromType
|| !aToType
|| !aListener
|| !_retval
) {
452 return NS_ERROR_NULL_POINTER
;
457 // first determine whether we can even handle this conversion
458 // build a CONTRACTID
459 nsAutoCString contractID
;
460 contractID
.AssignLiteral(NS_ISTREAMCONVERTER_KEY
"?from=");
461 contractID
.Append(aFromType
);
462 contractID
.AppendLiteral("&to=");
463 contractID
.Append(aToType
);
464 const char* cContractID
= contractID
.get();
466 nsCOMPtr
<nsIStreamConverter
> listener(do_CreateInstance(cContractID
, &rv
));
468 // couldn't go direct, let's try walking the graph of converters.
470 if (NS_FAILED(rv
)) return rv
;
472 nsTArray
<nsCString
>* converterChain
= nullptr;
474 rv
= FindConverter(cContractID
, &converterChain
);
476 // can't make this conversion.
477 // XXX should have a more descriptive error code.
478 return NS_ERROR_FAILURE
;
481 // aListener is the listener that wants the final, converted, data.
482 // we initialize finalListener w/ aListener so it gets put at the
483 // tail end of the chain, which in the loop below, means the *first*
484 // converter created.
485 nsCOMPtr
<nsIStreamListener
> finalListener
= aListener
;
487 // convert the stream using each edge of the graph as a step.
488 // this is our stream conversion traversal.
489 int32_t edgeCount
= int32_t(converterChain
->Length());
490 NS_ASSERTION(edgeCount
> 0, "findConverter should have failed");
491 for (int i
= 0; i
< edgeCount
; i
++) {
492 const char* lContractID
= converterChain
->ElementAt(i
).get();
494 // create the converter for this from/to pair
495 nsCOMPtr
<nsIStreamConverter
> converter(do_CreateInstance(lContractID
));
496 NS_ASSERTION(converter
,
497 "graph construction problem, built a contractid that wasn't "
500 nsAutoCString fromStr
, toStr
;
501 rv
= ParseFromTo(lContractID
, fromStr
, toStr
);
503 delete converterChain
;
507 // connect the converter w/ the listener that should get the converted
509 rv
= converter
->AsyncConvertData(fromStr
.get(), toStr
.get(),
510 finalListener
, aContext
);
512 delete converterChain
;
516 // the last iteration of this loop will result in finalListener
517 // pointing to the converter that "starts" the conversion chain.
518 // this converter's "from" type is the original "from" type. Prior
519 // to the last iteration, finalListener will continuously be wedged
520 // into the next listener in the chain, then be updated.
521 finalListener
= converter
;
523 delete converterChain
;
524 // return the first listener in the chain.
525 finalListener
.forget(_retval
);
527 // we're going direct.
528 rv
= listener
->AsyncConvertData(aFromType
, aToType
, aListener
, aContext
);
529 listener
.forget(_retval
);
535 nsresult
NS_NewStreamConv(nsStreamConverterService
** aStreamConv
) {
536 MOZ_ASSERT(aStreamConv
!= nullptr, "null ptr");
537 if (!aStreamConv
) return NS_ERROR_NULL_POINTER
;
539 RefPtr
<nsStreamConverterService
> conv
= new nsStreamConverterService();
540 conv
.forget(aStreamConv
);