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