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