nsStreamConverterService.cpp 19 KB

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