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