Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsSyncLoadService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
/*
7
 * A service that provides methods for synchronously loading a DOM in various ways.
8
 */
9
10
#include "nsSyncLoadService.h"
11
#include "nsCOMPtr.h"
12
#include "nsIChannel.h"
13
#include "nsIChannelEventSink.h"
14
#include "nsIAsyncVerifyRedirectCallback.h"
15
#include "nsIInterfaceRequestor.h"
16
#include "nsIStreamListener.h"
17
#include "nsIURI.h"
18
#include "nsString.h"
19
#include "nsWeakReference.h"
20
#include "nsIDocument.h"
21
#include "nsIPrincipal.h"
22
#include "nsContentUtils.h" // for kLoadAsData
23
#include "nsThreadUtils.h"
24
#include "nsNetUtil.h"
25
#include "nsStreamUtils.h"
26
#include <algorithm>
27
28
using namespace mozilla;
29
30
using mozilla::net::ReferrerPolicy;
31
32
/**
33
 * This class manages loading a single XML document
34
 */
35
36
class nsSyncLoader : public nsIStreamListener,
37
                     public nsIChannelEventSink,
38
                     public nsIInterfaceRequestor,
39
                     public nsSupportsWeakReference
40
{
41
public:
42
    nsSyncLoader()
43
      : mLoading(false)
44
      , mAsyncLoadStatus(NS_ERROR_NOT_INITIALIZED)
45
0
    {
46
0
    }
47
48
    NS_DECL_ISUPPORTS
49
50
    nsresult LoadDocument(nsIChannel* aChannel,
51
                          bool aChannelIsSync, bool aForceToXML,
52
                          ReferrerPolicy aReferrerPolicy,
53
                          nsIDocument** aResult);
54
55
    NS_FORWARD_NSISTREAMLISTENER(mListener->)
56
    NS_DECL_NSIREQUESTOBSERVER
57
58
    NS_DECL_NSICHANNELEVENTSINK
59
60
    NS_DECL_NSIINTERFACEREQUESTOR
61
62
private:
63
    virtual ~nsSyncLoader();
64
65
    nsresult PushAsyncStream(nsIStreamListener* aListener);
66
    nsresult PushSyncStream(nsIStreamListener* aListener);
67
68
    nsCOMPtr<nsIChannel> mChannel;
69
    nsCOMPtr<nsIStreamListener> mListener;
70
    bool mLoading;
71
    nsresult mAsyncLoadStatus;
72
};
73
74
class nsForceXMLListener : public nsIStreamListener
75
{
76
    virtual ~nsForceXMLListener();
77
78
public:
79
    explicit nsForceXMLListener(nsIStreamListener* aListener);
80
81
    NS_DECL_ISUPPORTS
82
    NS_FORWARD_NSISTREAMLISTENER(mListener->)
83
    NS_DECL_NSIREQUESTOBSERVER
84
85
private:
86
    nsCOMPtr<nsIStreamListener> mListener;
87
};
88
89
nsForceXMLListener::nsForceXMLListener(nsIStreamListener* aListener)
90
    : mListener(aListener)
91
0
{
92
0
}
93
94
nsForceXMLListener::~nsForceXMLListener()
95
0
{
96
0
}
97
98
NS_IMPL_ISUPPORTS(nsForceXMLListener,
99
                  nsIStreamListener,
100
                  nsIRequestObserver)
101
102
NS_IMETHODIMP
103
nsForceXMLListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
104
0
{
105
0
    nsresult status;
106
0
    aRequest->GetStatus(&status);
107
0
    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
108
0
    if (channel && NS_SUCCEEDED(status)) {
109
0
      channel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
110
0
    }
111
0
112
0
    return mListener->OnStartRequest(aRequest, aContext);
113
0
}
114
115
NS_IMETHODIMP
116
nsForceXMLListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
117
                                  nsresult aStatusCode)
118
0
{
119
0
    return mListener->OnStopRequest(aRequest, aContext, aStatusCode);
120
0
}
121
122
nsSyncLoader::~nsSyncLoader()
123
0
{
124
0
    if (mLoading && mChannel) {
125
0
        mChannel->Cancel(NS_BINDING_ABORTED);
126
0
    }
127
0
}
128
129
NS_IMPL_ISUPPORTS(nsSyncLoader,
130
                  nsIStreamListener,
131
                  nsIRequestObserver,
132
                  nsIChannelEventSink,
133
                  nsIInterfaceRequestor,
134
                  nsISupportsWeakReference)
135
136
nsresult
137
nsSyncLoader::LoadDocument(nsIChannel* aChannel,
138
                           bool aChannelIsSync,
139
                           bool aForceToXML,
140
                           ReferrerPolicy aReferrerPolicy,
141
                           nsIDocument **aResult)
142
0
{
143
0
    NS_ENSURE_ARG(aChannel);
144
0
    NS_ENSURE_ARG_POINTER(aResult);
145
0
    *aResult = nullptr;
146
0
    nsresult rv = NS_OK;
147
0
148
0
    mChannel = aChannel;
149
0
    nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(mChannel);
150
0
    if (http) {
151
0
        rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
152
0
                                    NS_LITERAL_CSTRING("text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
153
0
                                    false);
154
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
155
0
        nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
156
0
        if (loadInfo) {
157
0
            nsCOMPtr<nsIURI> loaderUri;
158
0
            loadInfo->TriggeringPrincipal()->GetURI(getter_AddRefs(loaderUri));
159
0
            if (loaderUri) {
160
0
                rv = http->SetReferrerWithPolicy(loaderUri, aReferrerPolicy);
161
0
                MOZ_ASSERT(NS_SUCCEEDED(rv));
162
0
            }
163
0
        }
164
0
    }
165
0
166
0
    // Hook us up to listen to redirects and the like.
167
0
    // Do this before setting up the cross-site proxy since
168
0
    // that installs its own proxies.
169
0
    mChannel->SetNotificationCallbacks(this);
170
0
171
0
    // Get the loadgroup of the channel
172
0
    nsCOMPtr<nsILoadGroup> loadGroup;
173
0
    rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
174
0
    NS_ENSURE_SUCCESS(rv, rv);
175
0
176
0
    // Create document
177
0
    nsCOMPtr<nsIDocument> document;
178
0
    rv = NS_NewXMLDocument(getter_AddRefs(document));
179
0
    NS_ENSURE_SUCCESS(rv, rv);
180
0
181
0
    // Start the document load. Do this before we attach the load listener
182
0
    // since we reset the document which drops all observers.
183
0
    nsCOMPtr<nsIStreamListener> listener;
184
0
    rv = document->StartDocumentLoad(kLoadAsData, mChannel,
185
0
                                     loadGroup, nullptr,
186
0
                                     getter_AddRefs(listener),
187
0
                                     true);
188
0
    NS_ENSURE_SUCCESS(rv, rv);
189
0
190
0
    if (aForceToXML) {
191
0
        nsCOMPtr<nsIStreamListener> forceListener =
192
0
            new nsForceXMLListener(listener);
193
0
        listener.swap(forceListener);
194
0
    }
195
0
196
0
    if (aChannelIsSync) {
197
0
        rv = PushSyncStream(listener);
198
0
    }
199
0
    else {
200
0
        rv = PushAsyncStream(listener);
201
0
    }
202
0
203
0
    http = do_QueryInterface(mChannel);
204
0
    if (NS_SUCCEEDED(rv) && http) {
205
0
        bool succeeded;
206
0
        if (NS_FAILED(http->GetRequestSucceeded(&succeeded)) || !succeeded) {
207
0
            rv = NS_ERROR_FAILURE;
208
0
        }
209
0
    }
210
0
    mChannel = nullptr;
211
0
212
0
    // check that the load succeeded
213
0
    NS_ENSURE_SUCCESS(rv, rv);
214
0
215
0
    NS_ENSURE_TRUE(document->GetRootElement(), NS_ERROR_FAILURE);
216
0
217
0
    document.forget(aResult);
218
0
219
0
    return NS_OK;
220
0
}
221
222
nsresult
223
nsSyncLoader::PushAsyncStream(nsIStreamListener* aListener)
224
0
{
225
0
    mListener = aListener;
226
0
227
0
    mAsyncLoadStatus = NS_OK;
228
0
229
0
    // Start reading from the channel
230
0
    nsresult rv = mChannel->AsyncOpen2(this);
231
0
232
0
    if (NS_SUCCEEDED(rv)) {
233
0
        // process events until we're finished.
234
0
        mLoading = true;
235
0
        nsIThread *thread = NS_GetCurrentThread();
236
0
        while (mLoading && NS_SUCCEEDED(rv)) {
237
0
            bool processedEvent;
238
0
            rv = thread->ProcessNextEvent(true, &processedEvent);
239
0
            if (NS_SUCCEEDED(rv) && !processedEvent)
240
0
                rv = NS_ERROR_UNEXPECTED;
241
0
        }
242
0
    }
243
0
244
0
    mListener = nullptr;
245
0
246
0
    NS_ENSURE_SUCCESS(rv, rv);
247
0
248
0
    // Note that if AsyncOpen failed that's ok -- the only caller of
249
0
    // this method nulls out mChannel immediately after we return.
250
0
251
0
    return mAsyncLoadStatus;
252
0
}
253
254
nsresult
255
nsSyncLoader::PushSyncStream(nsIStreamListener* aListener)
256
0
{
257
0
    nsCOMPtr<nsIInputStream> in;
258
0
    nsresult rv = mChannel->Open2(getter_AddRefs(in));
259
0
    NS_ENSURE_SUCCESS(rv, rv);
260
0
261
0
    mLoading = true;
262
0
    rv = nsSyncLoadService::PushSyncStreamToListener(in.forget(), aListener,
263
0
                                                     mChannel);
264
0
    mLoading = false;
265
0
266
0
    return rv;
267
0
}
268
269
NS_IMETHODIMP
270
nsSyncLoader::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
271
0
{
272
0
    return mListener->OnStartRequest(aRequest, aContext);
273
0
}
274
275
NS_IMETHODIMP
276
nsSyncLoader::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
277
                            nsresult aStatusCode)
278
0
{
279
0
    if (NS_SUCCEEDED(mAsyncLoadStatus) && NS_FAILED(aStatusCode)) {
280
0
        mAsyncLoadStatus = aStatusCode;
281
0
    }
282
0
    nsresult rv = mListener->OnStopRequest(aRequest, aContext, aStatusCode);
283
0
    if (NS_SUCCEEDED(mAsyncLoadStatus) && NS_FAILED(rv)) {
284
0
        mAsyncLoadStatus = rv;
285
0
    }
286
0
    mLoading = false;
287
0
288
0
    return rv;
289
0
}
290
291
NS_IMETHODIMP
292
nsSyncLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
293
                                     nsIChannel *aNewChannel,
294
                                     uint32_t aFlags,
295
                                     nsIAsyncVerifyRedirectCallback *callback)
296
0
{
297
0
    MOZ_ASSERT(aNewChannel, "Redirecting to null channel?");
298
0
299
0
    mChannel = aNewChannel;
300
0
301
0
    callback->OnRedirectVerifyCallback(NS_OK);
302
0
    return NS_OK;
303
0
}
304
305
NS_IMETHODIMP
306
nsSyncLoader::GetInterface(const nsIID & aIID,
307
                           void **aResult)
308
0
{
309
0
    return QueryInterface(aIID, aResult);
310
0
}
311
312
/* static */
313
nsresult
314
nsSyncLoadService::LoadDocument(nsIURI *aURI,
315
                                nsContentPolicyType aContentPolicyType,
316
                                nsIPrincipal *aLoaderPrincipal,
317
                                nsSecurityFlags aSecurityFlags,
318
                                nsILoadGroup *aLoadGroup,
319
                                bool aForceToXML,
320
                                ReferrerPolicy aReferrerPolicy,
321
                                nsIDocument** aResult)
322
0
{
323
0
    nsCOMPtr<nsIChannel> channel;
324
0
    nsresult rv = NS_NewChannel(getter_AddRefs(channel),
325
0
                                aURI,
326
0
                                aLoaderPrincipal,
327
0
                                aSecurityFlags,
328
0
                                aContentPolicyType,
329
0
                                nullptr, // PerformanceStorage
330
0
                                aLoadGroup);
331
0
    NS_ENSURE_SUCCESS(rv, rv);
332
0
333
0
    if (!aForceToXML) {
334
0
        channel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
335
0
    }
336
0
337
0
    bool isChrome = false, isResource = false;
338
0
    // if the load needs to enforce CORS, then force the load to be async
339
0
    bool isSync =
340
0
      !(aSecurityFlags & nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) &&
341
0
      ((NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) ||
342
0
       (NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)) && isResource));
343
0
    RefPtr<nsSyncLoader> loader = new nsSyncLoader();
344
0
    return loader->LoadDocument(channel, isSync, aForceToXML,
345
0
                                aReferrerPolicy, aResult);
346
0
}
347
348
/* static */
349
nsresult
350
nsSyncLoadService::PushSyncStreamToListener(already_AddRefed<nsIInputStream> aIn,
351
                                            nsIStreamListener* aListener,
352
                                            nsIChannel* aChannel)
353
0
{
354
0
    nsCOMPtr<nsIInputStream> in = std::move(aIn);
355
0
356
0
    // Set up buffering stream
357
0
    nsresult rv;
358
0
    nsCOMPtr<nsIInputStream> bufferedStream;
359
0
    if (!NS_InputStreamIsBuffered(in)) {
360
0
        int64_t chunkSize;
361
0
        rv = aChannel->GetContentLength(&chunkSize);
362
0
        if (NS_FAILED(rv) || chunkSize < 1) {
363
0
            chunkSize = 4096;
364
0
        }
365
0
        chunkSize = std::min(int64_t(UINT16_MAX), chunkSize);
366
0
367
0
        rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
368
0
                                       in.forget(), chunkSize);
369
0
        NS_ENSURE_SUCCESS(rv, rv);
370
0
371
0
        in = bufferedStream;
372
0
    }
373
0
374
0
    // Load
375
0
    rv = aListener->OnStartRequest(aChannel, nullptr);
376
0
    if (NS_SUCCEEDED(rv)) {
377
0
        uint64_t sourceOffset = 0;
378
0
        while (1) {
379
0
            uint64_t readCount = 0;
380
0
            rv = in->Available(&readCount);
381
0
            if (NS_FAILED(rv) || !readCount) {
382
0
                if (rv == NS_BASE_STREAM_CLOSED) {
383
0
                    // End of file, but not an error
384
0
                    rv = NS_OK;
385
0
                }
386
0
                break;
387
0
            }
388
0
389
0
            if (readCount > UINT32_MAX)
390
0
                readCount = UINT32_MAX;
391
0
392
0
            rv = aListener->OnDataAvailable(aChannel, nullptr, in,
393
0
                                            (uint32_t)std::min(sourceOffset, (uint64_t)UINT32_MAX),
394
0
                                            (uint32_t)readCount);
395
0
            if (NS_FAILED(rv)) {
396
0
                break;
397
0
            }
398
0
            sourceOffset += readCount;
399
0
        }
400
0
    }
401
0
    if (NS_FAILED(rv)) {
402
0
        aChannel->Cancel(rv);
403
0
    }
404
0
    aListener->OnStopRequest(aChannel, nullptr, rv);
405
0
406
0
    return rv;
407
0
}