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