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