1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "BlobURLInputStream.h"
8 #include "BlobURL.h"
9 #include "BlobURLChannel.h"
10 #include "BlobURLProtocolHandler.h"
11
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/dom/ContentChild.h"
14 #include "mozilla/dom/IPCBlobUtils.h"
15 #include "nsStreamUtils.h"
16
17 namespace mozilla::dom {
18
19 NS_IMPL_ADDREF(BlobURLInputStream);
20 NS_IMPL_RELEASE(BlobURLInputStream);
21
22 NS_INTERFACE_MAP_BEGIN(BlobURLInputStream)
NS_INTERFACE_MAP_ENTRY(nsIInputStream)23 NS_INTERFACE_MAP_ENTRY(nsIInputStream)
24 NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
25 NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength)
26 NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStreamLength)
27 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
28 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAsyncInputStream)
29 NS_INTERFACE_MAP_END
30
31 /* static */
32 already_AddRefed<nsIInputStream> BlobURLInputStream::Create(
33 BlobURLChannel* const aChannel, BlobURL* const aBlobURL) {
34 MOZ_ASSERT(NS_IsMainThread());
35
36 if (NS_WARN_IF(!aChannel) || NS_WARN_IF(!aBlobURL)) {
37 return nullptr;
38 }
39
40 nsAutoCString spec;
41
42 nsresult rv = aBlobURL->GetSpec(spec);
43 if (NS_WARN_IF(NS_FAILED(rv))) {
44 return nullptr;
45 }
46
47 return MakeAndAddRef<BlobURLInputStream>(aChannel, spec);
48 }
49
50 // from nsIInputStream interface
Close()51 NS_IMETHODIMP BlobURLInputStream::Close() {
52 return CloseWithStatus(NS_BASE_STREAM_CLOSED);
53 }
54
Available(uint64_t * aLength)55 NS_IMETHODIMP BlobURLInputStream::Available(uint64_t* aLength) {
56 MutexAutoLock lock(mStateMachineMutex);
57
58 if (mState == State::ERROR) {
59 MOZ_ASSERT(NS_FAILED(mError));
60 return mError;
61 }
62
63 if (mState == State::CLOSED) {
64 return NS_BASE_STREAM_CLOSED;
65 }
66
67 if (mState == State::READY) {
68 MOZ_ASSERT(mAsyncInputStream);
69 return mAsyncInputStream->Available(aLength);
70 }
71
72 return NS_BASE_STREAM_WOULD_BLOCK;
73 }
74
Read(char * aBuffer,uint32_t aCount,uint32_t * aReadCount)75 NS_IMETHODIMP BlobURLInputStream::Read(char* aBuffer, uint32_t aCount,
76 uint32_t* aReadCount) {
77 MutexAutoLock lock(mStateMachineMutex);
78 if (mState == State::ERROR) {
79 MOZ_ASSERT(NS_FAILED(mError));
80 return mError;
81 }
82
83 // Read() should not return NS_BASE_STREAM_CLOSED if stream is closed.
84 // A read count of 0 should indicate closed or consumed stream.
85 // See:
86 // https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/xpcom/io/nsIInputStream.idl#104
87 if (mState == State::CLOSED) {
88 *aReadCount = 0;
89 return NS_OK;
90 }
91
92 if (mState == State::READY) {
93 MOZ_ASSERT(mAsyncInputStream);
94 nsresult rv = mAsyncInputStream->Read(aBuffer, aCount, aReadCount);
95 if (NS_SUCCEEDED(rv) && aReadCount && !*aReadCount) {
96 mState = State::CLOSED;
97 ReleaseUnderlyingStream(lock);
98 }
99 return rv;
100 }
101
102 return NS_BASE_STREAM_WOULD_BLOCK;
103 }
104
ReadSegments(nsWriteSegmentFun aWriter,void * aClosure,uint32_t aCount,uint32_t * aResult)105 NS_IMETHODIMP BlobURLInputStream::ReadSegments(nsWriteSegmentFun aWriter,
106 void* aClosure, uint32_t aCount,
107 uint32_t* aResult) {
108 // This means the caller will have to wrap the stream in an
109 // nsBufferedInputStream in order to use ReadSegments
110 return NS_ERROR_NOT_IMPLEMENTED;
111 }
112
IsNonBlocking(bool * aNonBlocking)113 NS_IMETHODIMP BlobURLInputStream::IsNonBlocking(bool* aNonBlocking) {
114 *aNonBlocking = true;
115 return NS_OK;
116 }
117
118 // from nsIAsyncInputStream interface
CloseWithStatus(nsresult aStatus)119 NS_IMETHODIMP BlobURLInputStream::CloseWithStatus(nsresult aStatus) {
120 MutexAutoLock lock(mStateMachineMutex);
121 if (mState == State::READY) {
122 MOZ_ASSERT(mAsyncInputStream);
123 mAsyncInputStream->CloseWithStatus(aStatus);
124 }
125
126 mState = State::CLOSED;
127 ReleaseUnderlyingStream(lock);
128 return NS_OK;
129 }
130
AsyncWait(nsIInputStreamCallback * aCallback,uint32_t aFlags,uint32_t aRequestedCount,nsIEventTarget * aEventTarget)131 NS_IMETHODIMP BlobURLInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
132 uint32_t aFlags,
133 uint32_t aRequestedCount,
134 nsIEventTarget* aEventTarget) {
135 MutexAutoLock lock(mStateMachineMutex);
136
137 if (mState == State::ERROR) {
138 MOZ_ASSERT(NS_FAILED(mError));
139 return NS_ERROR_FAILURE;
140 }
141
142 // Pre-empting a valid callback with another is not allowed.
143 if (NS_WARN_IF(mAsyncWaitCallback && aCallback)) {
144 return NS_ERROR_FAILURE;
145 }
146
147 mAsyncWaitTarget = aEventTarget;
148 mAsyncWaitRequestedCount = aRequestedCount;
149 mAsyncWaitFlags = aFlags;
150 mAsyncWaitCallback = aCallback;
151
152 if (mState == State::INITIAL) {
153 mState = State::WAITING;
154 // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds
155 // or fails
156 if (NS_IsMainThread()) {
157 RetrieveBlobData(lock);
158 return NS_OK;
159 }
160
161 nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod(
162 "BlobURLInputStream::CallRetrieveBlobData", this,
163 &BlobURLInputStream::CallRetrieveBlobData);
164 NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL);
165 return NS_OK;
166 }
167
168 if (mState == State::WAITING) {
169 // RetrieveBlobData is already in progress and will execute
170 // NotifyWaitTargets when retrieve succeeds or fails
171 return NS_OK;
172 }
173
174 if (mState == State::READY) {
175 // Ask the blob's input stream if reading is possible or not
176 return mAsyncInputStream->AsyncWait(
177 mAsyncWaitCallback ? this : nullptr, mAsyncWaitFlags,
178 mAsyncWaitRequestedCount, mAsyncWaitTarget);
179 }
180
181 MOZ_ASSERT(mState == State::CLOSED);
182 NotifyWaitTargets(lock);
183 return NS_OK;
184 }
185
186 // from nsIInputStreamLength interface
Length(int64_t * aLength)187 NS_IMETHODIMP BlobURLInputStream::Length(int64_t* aLength) {
188 MutexAutoLock lock(mStateMachineMutex);
189
190 if (mState == State::CLOSED) {
191 return NS_BASE_STREAM_CLOSED;
192 }
193
194 if (mState == State::ERROR) {
195 MOZ_ASSERT(NS_FAILED(mError));
196 return NS_ERROR_FAILURE;
197 }
198
199 if (mState == State::READY) {
200 *aLength = mBlobSize;
201 return NS_OK;
202 }
203 return NS_BASE_STREAM_WOULD_BLOCK;
204 }
205
206 // from nsIAsyncInputStreamLength interface
AsyncLengthWait(nsIInputStreamLengthCallback * aCallback,nsIEventTarget * aEventTarget)207 NS_IMETHODIMP BlobURLInputStream::AsyncLengthWait(
208 nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget) {
209 MutexAutoLock lock(mStateMachineMutex);
210
211 if (mState == State::ERROR) {
212 MOZ_ASSERT(NS_FAILED(mError));
213 return mError;
214 }
215
216 // Pre-empting a valid callback with another is not allowed.
217 if (mAsyncLengthWaitCallback && aCallback) {
218 return NS_ERROR_FAILURE;
219 }
220
221 mAsyncLengthWaitTarget = aEventTarget;
222 mAsyncLengthWaitCallback = aCallback;
223
224 if (mState == State::INITIAL) {
225 mState = State::WAITING;
226 // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds
227 // or fails
228 if (NS_IsMainThread()) {
229 RetrieveBlobData(lock);
230 return NS_OK;
231 }
232
233 nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod(
234 "BlobURLInputStream::CallRetrieveBlobData", this,
235 &BlobURLInputStream::CallRetrieveBlobData);
236 NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL);
237 return NS_OK;
238 }
239
240 if (mState == State::WAITING) {
241 // RetrieveBlobData is already in progress and will execute
242 // NotifyWaitTargets when retrieve succeeds or fails
243 return NS_OK;
244 }
245
246 // Since here the state must be READY (in which case the size of the blob is
247 // already known) or CLOSED, callback can be called immediately
248 NotifyWaitTargets(lock);
249 return NS_OK;
250 }
251
252 // from nsIInputStreamCallback interface
OnInputStreamReady(nsIAsyncInputStream * aStream)253 NS_IMETHODIMP BlobURLInputStream::OnInputStreamReady(
254 nsIAsyncInputStream* aStream) {
255 nsCOMPtr<nsIInputStreamCallback> callback;
256
257 {
258 MutexAutoLock lock(mStateMachineMutex);
259 MOZ_ASSERT_IF(mAsyncInputStream, aStream == mAsyncInputStream);
260
261 // aborted in the meantime
262 if (!mAsyncWaitCallback) {
263 return NS_OK;
264 }
265
266 mAsyncWaitCallback.swap(callback);
267 mAsyncWaitTarget = nullptr;
268 }
269
270 MOZ_ASSERT(callback);
271 return callback->OnInputStreamReady(this);
272 }
273
274 // from nsIInputStreamLengthCallback interface
OnInputStreamLengthReady(nsIAsyncInputStreamLength * aStream,int64_t aLength)275 NS_IMETHODIMP BlobURLInputStream::OnInputStreamLengthReady(
276 nsIAsyncInputStreamLength* aStream, int64_t aLength) {
277 nsCOMPtr<nsIInputStreamLengthCallback> callback;
278 {
279 MutexAutoLock lock(mStateMachineMutex);
280
281 // aborted in the meantime
282 if (!mAsyncLengthWaitCallback) {
283 return NS_OK;
284 }
285
286 mAsyncLengthWaitCallback.swap(callback);
287 mAsyncLengthWaitCallback = nullptr;
288 }
289
290 return callback->OnInputStreamLengthReady(this, aLength);
291 }
292
293 // private:
~BlobURLInputStream()294 BlobURLInputStream::~BlobURLInputStream() {
295 if (mChannel) {
296 NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget());
297 }
298 }
299
BlobURLInputStream(BlobURLChannel * const aChannel,nsACString & aBlobURLSpec)300 BlobURLInputStream::BlobURLInputStream(BlobURLChannel* const aChannel,
301 nsACString& aBlobURLSpec)
302 : mChannel(aChannel),
303 mBlobURLSpec(std::move(aBlobURLSpec)),
304 mStateMachineMutex("BlobURLInputStream::mStateMachineMutex"),
305 mState(State::INITIAL),
306 mError(NS_OK),
307 mBlobSize(-1),
308 mAsyncWaitFlags(),
309 mAsyncWaitRequestedCount() {}
310
WaitOnUnderlyingStream(const MutexAutoLock & aProofOfLock)311 void BlobURLInputStream::WaitOnUnderlyingStream(
312 const MutexAutoLock& aProofOfLock) {
313 if (mAsyncWaitCallback || mAsyncWaitTarget) {
314 // AsyncWait should be called on the underlying stream
315 mAsyncInputStream->AsyncWait(mAsyncWaitCallback ? this : nullptr,
316 mAsyncWaitFlags, mAsyncWaitRequestedCount,
317 mAsyncWaitTarget);
318 }
319
320 if (mAsyncLengthWaitCallback || mAsyncLengthWaitTarget) {
321 // AsyncLengthWait should be called on the underlying stream
322 nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength =
323 do_QueryInterface(mAsyncInputStream);
324 if (asyncStreamLength) {
325 asyncStreamLength->AsyncLengthWait(
326 mAsyncLengthWaitCallback ? this : nullptr, mAsyncLengthWaitTarget);
327 }
328 }
329 }
330
CallRetrieveBlobData()331 void BlobURLInputStream::CallRetrieveBlobData() {
332 MutexAutoLock lock(mStateMachineMutex);
333 RetrieveBlobData(lock);
334 }
335
RetrieveBlobData(const MutexAutoLock & aProofOfLock)336 void BlobURLInputStream::RetrieveBlobData(const MutexAutoLock& aProofOfLock) {
337 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
338
339 MOZ_ASSERT(mState == State::WAITING);
340
341 auto cleanupOnEarlyExit = MakeScopeExit([&] {
342 mState = State::ERROR;
343 mError = NS_ERROR_FAILURE;
344 NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget());
345 NotifyWaitTargets(aProofOfLock);
346 });
347
348 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
349 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
350 nsCOMPtr<nsIPrincipal> loadingPrincipal;
351 if (NS_WARN_IF(NS_FAILED(loadInfo->GetTriggeringPrincipal(
352 getter_AddRefs(triggeringPrincipal)))) ||
353 NS_WARN_IF(!triggeringPrincipal)) {
354 NS_WARNING("Failed to get owning channel's triggering principal");
355 return;
356 }
357
358 if (NS_WARN_IF(NS_FAILED(
359 loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal))))) {
360 NS_WARNING("Failed to get owning channel's loading principal");
361 return;
362 }
363
364 Maybe<nsID> agentClusterId;
365 Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
366 if (clientInfo.isSome()) {
367 agentClusterId = clientInfo->AgentClusterId();
368 }
369
370 if (XRE_IsParentProcess() || !BlobURLSchemeIsHTTPOrHTTPS(mBlobURLSpec)) {
371 RefPtr<BlobImpl> blobImpl;
372
373 // Since revoked blobs are also retrieved, it is possible that the blob no
374 // longer exists (due to the 5 second timeout) when execution reaches here
375 if (!BlobURLProtocolHandler::GetDataEntry(
376 mBlobURLSpec, getter_AddRefs(blobImpl), loadingPrincipal,
377 triggeringPrincipal, loadInfo->GetOriginAttributes(),
378 loadInfo->GetInnerWindowID(), agentClusterId,
379 true /* AlsoIfRevoked */)) {
380 NS_WARNING("Failed to get data entry principal. URL revoked?");
381 return;
382 }
383
384 if (NS_WARN_IF(
385 NS_FAILED(StoreBlobImplStream(blobImpl.forget(), aProofOfLock)))) {
386 return;
387 }
388
389 mState = State::READY;
390
391 // By design, execution can only reach here when a caller has called
392 // AsyncWait or AsyncLengthWait on this stream. The underlying stream is
393 // valid, but the caller should not be informed until that stream has data
394 // to read or it is closed.
395 WaitOnUnderlyingStream(aProofOfLock);
396
397 cleanupOnEarlyExit.release();
398 return;
399 }
400
401 ContentChild* contentChild{ContentChild::GetSingleton()};
402 MOZ_ASSERT(contentChild);
403
404 const RefPtr<BlobURLInputStream> self = this;
405
406 cleanupOnEarlyExit.release();
407
408 contentChild
409 ->SendBlobURLDataRequest(mBlobURLSpec, triggeringPrincipal,
410 loadingPrincipal,
411 loadInfo->GetOriginAttributes(),
412 loadInfo->GetInnerWindowID(), agentClusterId)
413 ->Then(
414 GetCurrentSerialEventTarget(), __func__,
415 [self](const BlobURLDataRequestResult& aResult) {
416 MutexAutoLock lock(self->mStateMachineMutex);
417 if (aResult.type() == BlobURLDataRequestResult::TIPCBlob) {
418 if (self->mState == State::WAITING) {
419 RefPtr<BlobImpl> blobImpl =
420 IPCBlobUtils::Deserialize(aResult.get_IPCBlob());
421 if (blobImpl && self->StoreBlobImplStream(blobImpl.forget(),
422 lock) == NS_OK) {
423 self->mState = State::READY;
424 // By design, execution can only reach here when a caller has
425 // called AsyncWait or AsyncLengthWait on this stream. The
426 // underlying stream is valid, but the caller should not be
427 // informed until that stream has data to read or it is
428 // closed.
429 self->WaitOnUnderlyingStream(lock);
430 return;
431 }
432 } else {
433 MOZ_ASSERT(self->mState == State::CLOSED);
434 // Callback can be called immediately
435 self->NotifyWaitTargets(lock);
436 return;
437 }
438 }
439 NS_WARNING("Blob data was not retrieved!");
440 self->mState = State::ERROR;
441 self->mError = aResult.type() == BlobURLDataRequestResult::Tnsresult
442 ? aResult.get_nsresult()
443 : NS_ERROR_FAILURE;
444 NS_ReleaseOnMainThread("BlobURLInputStream::mChannel",
445 self->mChannel.forget());
446 self->NotifyWaitTargets(lock);
447 },
448 [self](mozilla::ipc::ResponseRejectReason aReason) {
449 MutexAutoLock lock(self->mStateMachineMutex);
450 NS_WARNING("IPC call to SendBlobURLDataRequest failed!");
451 self->mState = State::ERROR;
452 self->mError = NS_ERROR_FAILURE;
453 NS_ReleaseOnMainThread("BlobURLInputStream::mChannel",
454 self->mChannel.forget());
455 self->NotifyWaitTargets(lock);
456 });
457 }
458
StoreBlobImplStream(already_AddRefed<BlobImpl> aBlobImpl,const MutexAutoLock & aProofOfLock)459 nsresult BlobURLInputStream::StoreBlobImplStream(
460 already_AddRefed<BlobImpl> aBlobImpl, const MutexAutoLock& aProofOfLock) {
461 MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
462 const RefPtr<BlobImpl> blobImpl = aBlobImpl;
463 nsAutoString contentType;
464 blobImpl->GetType(contentType);
465 mChannel->SetContentType(NS_ConvertUTF16toUTF8(contentType));
466
467 auto cleanupOnExit = MakeScopeExit([&] { mChannel = nullptr; });
468
469 if (blobImpl->IsFile()) {
470 nsAutoString filename;
471 blobImpl->GetName(filename);
472
473 // Don't overwrite existing name.
474 nsString ignored;
475 bool hasName =
476 NS_SUCCEEDED(mChannel->GetContentDispositionFilename(ignored));
477
478 if (!filename.IsEmpty() && !hasName) {
479 mChannel->SetContentDispositionFilename(filename);
480 }
481 }
482
483 mozilla::ErrorResult errorResult;
484
485 mBlobSize = blobImpl->GetSize(errorResult);
486
487 if (NS_WARN_IF(errorResult.Failed())) {
488 return errorResult.StealNSResult();
489 }
490
491 mChannel->SetContentLength(mBlobSize);
492
493 nsCOMPtr<nsIInputStream> inputStream;
494 blobImpl->CreateInputStream(getter_AddRefs(inputStream), errorResult);
495
496 if (NS_WARN_IF(errorResult.Failed())) {
497 return errorResult.StealNSResult();
498 }
499
500 if (NS_WARN_IF(!inputStream)) {
501 return NS_ERROR_NOT_AVAILABLE;
502 }
503
504 nsresult rv = NS_MakeAsyncNonBlockingInputStream(
505 inputStream.forget(), getter_AddRefs(mAsyncInputStream));
506 if (NS_WARN_IF(NS_FAILED(rv))) {
507 return rv;
508 }
509
510 if (NS_WARN_IF(!mAsyncInputStream)) {
511 return NS_ERROR_NOT_AVAILABLE;
512 }
513
514 return NS_OK;
515 }
516
NotifyWaitTargets(const MutexAutoLock & aProofOfLock)517 void BlobURLInputStream::NotifyWaitTargets(const MutexAutoLock& aProofOfLock) {
518 if (mAsyncWaitCallback) {
519 auto callback = mAsyncWaitTarget
520 ? NS_NewInputStreamReadyEvent(
521 "BlobURLInputStream::OnInputStreamReady",
522 mAsyncWaitCallback, mAsyncWaitTarget)
523 : mAsyncWaitCallback;
524
525 mAsyncWaitCallback = nullptr;
526 mAsyncWaitTarget = nullptr;
527 callback->OnInputStreamReady(this);
528 }
529
530 if (mAsyncLengthWaitCallback) {
531 const RefPtr<BlobURLInputStream> self = this;
532 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
533 "BlobURLInputStream::OnInputStreamLengthReady", [self] {
534 self->mAsyncLengthWaitCallback->OnInputStreamLengthReady(
535 self, self->mBlobSize);
536 });
537
538 mAsyncLengthWaitCallback = nullptr;
539
540 if (mAsyncLengthWaitTarget) {
541 mAsyncLengthWaitTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
542 mAsyncLengthWaitTarget = nullptr;
543 } else {
544 runnable->Run();
545 }
546 }
547 }
548
ReleaseUnderlyingStream(const MutexAutoLock & aProofOfLock)549 void BlobURLInputStream::ReleaseUnderlyingStream(
550 const MutexAutoLock& aProofOfLock) {
551 mAsyncInputStream = nullptr;
552 mBlobSize = -1;
553 }
554
555 } // namespace mozilla::dom
556