Coverage Report

Created: 2018-09-25 14:53

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