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