nsStreamConverterService.cpp (18298B)
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 "nsTArray.h" 19 #include "nsServiceManagerUtils.h" 20 #include "nsISimpleEnumerator.h" 21 #include "mozilla/Components.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 37 explicit BFSTableData(const nsACString& aKey) 38 : key(aKey), color(white), distance(-1) {} 39 }; 40 41 //////////////////////////////////////////////////////////// 42 // nsISupports methods 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 mozilla::components::CategoryManager::Service(&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. 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 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: 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. 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 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 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 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 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 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 }