/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Blob.h" #include "EmptyBlobImpl.h" #include "File.h" #include "MemoryBlobImpl.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/HoldDropJSObjects.h" #include "MultipartBlobImpl.h" #include "nsCycleCollectionParticipant.h" #include "nsIGlobalObject.h" #include "nsIInputStream.h" #include "nsPIDOMWindow.h" #include "StreamBlobImpl.h" #include "StringBlobImpl.h" #include "js/GCAPI.h" namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_CLASS(Blob) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Blob) NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Blob) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Blob) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Blob) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY_CONCRETE(Blob) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(Blob) NS_IMPL_CYCLE_COLLECTING_RELEASE(Blob) void Blob::MakeValidBlobType(nsAString& aType) { char16_t* iter = aType.BeginWriting(); char16_t* end = aType.EndWriting(); for (; iter != end; ++iter) { char16_t c = *iter; if (c < 0x20 || c > 0x7E) { // Non-ASCII char, bail out. aType.Truncate(); return; } if (c >= 'A' && c <= 'Z') { *iter = c + ('a' - 'A'); } } } /* static */ Blob* Blob::Create(nsIGlobalObject* aGlobal, BlobImpl* aImpl) { MOZ_ASSERT(aImpl); MOZ_ASSERT(aGlobal); if (NS_WARN_IF(!aGlobal)) { return nullptr; } return aImpl->IsFile() ? new File(aGlobal, aImpl) : new Blob(aGlobal, aImpl); } /* static */ already_AddRefed Blob::CreateStringBlob(nsIGlobalObject* aGlobal, const nsACString& aData, const nsAString& aContentType) { MOZ_ASSERT(aGlobal); if (NS_WARN_IF(!aGlobal)) { return nullptr; } RefPtr blobImpl = StringBlobImpl::Create(aData, aContentType); RefPtr blob = Blob::Create(aGlobal, blobImpl); MOZ_ASSERT(!blob->mImpl->IsFile()); return blob.forget(); } /* static */ already_AddRefed Blob::CreateMemoryBlob(nsIGlobalObject* aGlobal, void* aMemoryBuffer, uint64_t aLength, const nsAString& aContentType) { MOZ_ASSERT(aGlobal); if (NS_WARN_IF(!aGlobal)) { return nullptr; } RefPtr blob = Blob::Create( aGlobal, new MemoryBlobImpl(aMemoryBuffer, aLength, aContentType)); MOZ_ASSERT(!blob->mImpl->IsFile()); return blob.forget(); } Blob::Blob(nsIGlobalObject* aGlobal, BlobImpl* aImpl) : mImpl(aImpl), mGlobal(aGlobal) { MOZ_ASSERT(mImpl); MOZ_ASSERT(mGlobal); } Blob::~Blob() = default; bool Blob::IsFile() const { return mImpl->IsFile(); } const nsTArray>* Blob::GetSubBlobImpls() const { return mImpl->GetSubBlobImpls(); } already_AddRefed Blob::ToFile() { if (!mImpl->IsFile()) { return nullptr; } RefPtr file; if (HasFileInterface()) { file = static_cast(this); } else { file = new File(mGlobal, mImpl); } return file.forget(); } already_AddRefed Blob::ToFile(const nsAString& aName, ErrorResult& aRv) const { AutoTArray, 1> blobImpls({mImpl}); nsAutoString contentType; mImpl->GetType(contentType); RefPtr impl = MultipartBlobImpl::Create(std::move(blobImpls), aName, contentType, mGlobal->GetRTPCallerType(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RefPtr file = new File(mGlobal, impl); return file.forget(); } already_AddRefed Blob::CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType, ErrorResult& aRv) const { RefPtr impl = mImpl->CreateSlice(aStart, aLength, aContentType, aRv); if (aRv.Failed()) { return nullptr; } RefPtr blob = Blob::Create(mGlobal, impl); return blob.forget(); } uint64_t Blob::GetSize(ErrorResult& aRv) { return mImpl->GetSize(aRv); } void Blob::GetType(nsAString& aType) { mImpl->GetType(aType); } void Blob::GetBlobImplType(nsAString& aBlobImplType) { mImpl->GetBlobImplType(aBlobImplType); } already_AddRefed Blob::Slice(const Optional& aStart, const Optional& aEnd, const Optional& aContentType, ErrorResult& aRv) { nsAutoString contentType; if (aContentType.WasPassed()) { contentType = aContentType.Value(); } RefPtr impl = mImpl->Slice(aStart, aEnd, contentType, aRv); if (aRv.Failed()) { return nullptr; } RefPtr blob = Blob::Create(mGlobal, impl); return blob.forget(); } size_t Blob::GetAllocationSize() const { return mImpl->GetAllocationSize(); } // contentTypeWithCharset can be set to the contentType or // contentType+charset based on what the spec says. // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract nsresult Blob::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, nsACString& aContentType, nsACString& aCharset) const { return mImpl->GetSendInfo(aBody, aContentLength, aContentType, aCharset); } JSObject* Blob::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return Blob_Binding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed Blob::Constructor( const GlobalObject& aGlobal, const Optional>& aData, const BlobPropertyBag& aBag, ErrorResult& aRv) { RefPtr impl = new MultipartBlobImpl(); nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); if (aData.WasPassed()) { nsAutoString type(aBag.mType); MakeValidBlobType(type); impl->InitializeBlob(aData.Value(), type, aBag.mEndings == EndingType::Native, global->GetRTPCallerType(), aRv); } else { impl->InitializeBlob(global->GetRTPCallerType(), aRv); } if (NS_WARN_IF(aRv.Failed())) { return nullptr; } MOZ_ASSERT(!impl->IsFile()); RefPtr blob = Blob::Create(global, impl); return blob.forget(); } int64_t Blob::GetFileId() const { return mImpl->GetFileId(); } bool Blob::IsMemoryFile() const { return mImpl->IsMemoryFile(); } void Blob::CreateInputStream(nsIInputStream** aStream, ErrorResult& aRv) const { mImpl->CreateInputStream(aStream, aRv); } size_t BindingJSObjectMallocBytes(Blob* aBlob) { MOZ_ASSERT(aBlob); // TODO: The hazard analysis currently can't see that none of the // implementations of the GetAllocationSize virtual method call can GC (see // bug 1531951). JS::AutoSuppressGCAnalysis nogc; return aBlob->GetAllocationSize(); } already_AddRefed Blob::Text(ErrorResult& aRv) const { return ConsumeBody(BodyConsumer::CONSUME_TEXT, aRv); } already_AddRefed Blob::ArrayBuffer(ErrorResult& aRv) const { return ConsumeBody(BodyConsumer::CONSUME_ARRAYBUFFER, aRv); } already_AddRefed Blob::ConsumeBody( BodyConsumer::ConsumeType aConsumeType, ErrorResult& aRv) const { if (NS_WARN_IF(!mGlobal)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr mainThreadEventTarget; if (!NS_IsMainThread()) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); mainThreadEventTarget = workerPrivate->MainThreadEventTarget(); } else { mainThreadEventTarget = mGlobal->EventTargetFor(TaskCategory::Other); } MOZ_ASSERT(mainThreadEventTarget); nsCOMPtr inputStream; CreateInputStream(getter_AddRefs(inputStream), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return BodyConsumer::Create(mGlobal, mainThreadEventTarget, inputStream, nullptr, aConsumeType, VoidCString(), VoidString(), VoidCString(), VoidCString(), MutableBlobStorage::eOnlyInMemory, aRv); } // https://w3c.github.io/FileAPI/#stream-method-algo // "The stream() method, when invoked, must return the result of calling get // stream on this." // And that's https://w3c.github.io/FileAPI/#blob-get-stream. already_AddRefed Blob::Stream(JSContext* aCx, ErrorResult& aRv) const { nsCOMPtr stream; CreateInputStream(getter_AddRefs(stream), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (NS_WARN_IF(!mGlobal)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } auto algorithms = MakeRefPtr(*stream); // Step 1: Let stream be a new ReadableStream created in blob’s relevant // Realm. // Step 2: Set up stream with byte reading support. // Step 3: ... // (The spec here does not define pullAlgorithm and instead greedily enqueues // everything into the stream when .stream() is called, but here we only reads // the data when actual read request happens, via // InputToReadableStreamAlgorithms. See // https://github.com/w3c/FileAPI/issues/194.) RefPtr body = ReadableStream::CreateByteNative( aCx, mGlobal, *algorithms, Nothing(), aRv); if (aRv.Failed()) { return nullptr; } return body.forget(); } } // namespace mozilla::dom