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