/* * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "Blob.h" #include "BlobBuilder.h" #include "BlobLoader.h" #include "BlobPart.h" #include "BlobURL.h" #include "File.h" #include "JSDOMPromiseDeferred.h" #include "PolicyContainer.h" #include "ReadableStream.h" #include "ReadableStreamSource.h" #include "ScriptExecutionContext.h" #include "SharedBuffer.h" #include "ThreadableBlobRegistry.h" #include #include #include #include namespace WebCore { WTF_MAKE_ISO_ALLOCATED_IMPL(Blob); class BlobURLRegistry final : public URLRegistry { public: void registerURL(ScriptExecutionContext&, const URL&, URLRegistrable&) final; void unregisterURL(const URL&) final; static URLRegistry& registry(); }; void BlobURLRegistry::registerURL(ScriptExecutionContext& context, const URL& publicURL, URLRegistrable& blob) { ASSERT(&blob.registry() == this); ThreadableBlobRegistry::registerBlobURL(context.securityOrigin(), context.policyContainer(), publicURL, static_cast(blob).url()); } void BlobURLRegistry::unregisterURL(const URL& url) { ThreadableBlobRegistry::unregisterBlobURL(url); } URLRegistry& BlobURLRegistry::registry() { static NeverDestroyed instance; return instance; } Blob::Blob(UninitializedContructor, ScriptExecutionContext* context, URL&& url, String&& type) : ActiveDOMObject(context) , m_type(WTFMove(type)) , m_internalURL(WTFMove(url)) { } Blob::Blob(ScriptExecutionContext* context) : ActiveDOMObject(context) , m_size(0) , m_internalURL(BlobURL::createInternalURL()) { ThreadableBlobRegistry::registerBlobURL(m_internalURL, { }, { }); } static Vector buildBlobData(Vector&& blobPartVariants, const BlobPropertyBag& propertyBag) { BlobBuilder builder(propertyBag.endings); for (auto& blobPartVariant : blobPartVariants) { WTF::switchOn(blobPartVariant, [&] (auto& part) { builder.append(WTFMove(part)); } ); } return builder.finalize(); } Blob::Blob(ScriptExecutionContext& context, Vector&& blobPartVariants, const BlobPropertyBag& propertyBag) : ActiveDOMObject(&context) , m_type(normalizedContentType(propertyBag.type)) , m_internalURL(BlobURL::createInternalURL()) { ThreadableBlobRegistry::registerBlobURL(m_internalURL, buildBlobData(WTFMove(blobPartVariants), propertyBag), m_type); } Blob::Blob(ScriptExecutionContext* context, Vector&& data, const String& contentType) : ActiveDOMObject(context) , m_type(contentType) , m_size(data.size()) , m_internalURL(BlobURL::createInternalURL()) { ThreadableBlobRegistry::registerBlobURL(m_internalURL, { BlobPart(WTFMove(data)) }, contentType); } Blob::Blob(ReferencingExistingBlobConstructor, ScriptExecutionContext* context, const Blob& blob) : ActiveDOMObject(context) , m_type(blob.type()) , m_size(blob.size()) , m_internalURL(BlobURL::createInternalURL()) { ThreadableBlobRegistry::registerBlobURL(m_internalURL, { BlobPart(blob.url()) } , m_type); } Blob::Blob(DeserializationContructor, ScriptExecutionContext* context, const URL& srcURL, const String& type, std::optional size, const String& fileBackedPath) : ActiveDOMObject(context) , m_type(normalizedContentType(type)) , m_size(size) , m_internalURL(BlobURL::createInternalURL()) { if (fileBackedPath.isEmpty()) ThreadableBlobRegistry::registerBlobURL(nullptr, { }, m_internalURL, srcURL); else ThreadableBlobRegistry::registerBlobURLOptionallyFileBacked(m_internalURL, srcURL, fileBackedPath, m_type); } Blob::Blob(ScriptExecutionContext* context, const URL& srcURL, long long start, long long end, const String& type) : ActiveDOMObject(context) , m_type(normalizedContentType(type)) , m_internalURL(BlobURL::createInternalURL()) // m_size is not necessarily equal to end - start so we do not initialize it here. { ThreadableBlobRegistry::registerBlobURLForSlice(m_internalURL, srcURL, start, end, m_type); } Blob::~Blob() { while (!m_blobLoaders.isEmpty()) (*m_blobLoaders.begin())->cancel(); } Ref Blob::slice(ScriptExecutionContext& context, long long start, long long end, const String& contentType) const { auto blob = adoptRef(*new Blob(&context, m_internalURL, start, end, contentType)); blob->suspendIfNeeded(); return blob; } unsigned long long Blob::size() const { if (!m_size) { // FIXME: JavaScript cannot represent sizes as large as unsigned long long, we need to // come up with an exception to throw if file size is not representable. unsigned long long actualSize = ThreadableBlobRegistry::blobSize(m_internalURL); m_size = isInBounds(actualSize) ? actualSize : 0; } return *m_size; } bool Blob::isValidContentType(const String& contentType) { // FIXME: Do we really want to treat the empty string and null string as valid content types? unsigned length = contentType.length(); for (unsigned i = 0; i < length; ++i) { if (contentType[i] < 0x20 || contentType[i] > 0x7e) return false; } return true; } String Blob::normalizedContentType(const String& contentType) { if (!isValidContentType(contentType)) return emptyString(); return contentType.convertToASCIILowercase(); } void Blob::loadBlob(ScriptExecutionContext& context, FileReaderLoader::ReadType readType, CompletionHandler&& completionHandler) { auto blobLoader = makeUnique([this, pendingActivity = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](BlobLoader& blobLoader) mutable { completionHandler(blobLoader); m_blobLoaders.take(&blobLoader); }); blobLoader->start(*this, &context, readType); if (blobLoader->isLoading()) m_blobLoaders.add(WTFMove(blobLoader)); } void Blob::text(ScriptExecutionContext& context, Ref&& promise) { loadBlob(context, FileReaderLoader::ReadAsText, [promise = WTFMove(promise)](BlobLoader& blobLoader) mutable { if (auto optionalErrorCode = blobLoader.errorCode()) { promise->reject(Exception { *optionalErrorCode }); return; } promise->resolve(blobLoader.stringResult()); }); } void Blob::arrayBuffer(ScriptExecutionContext& context, Ref&& promise) { loadBlob(context, FileReaderLoader::ReadAsArrayBuffer, [promise = WTFMove(promise)](BlobLoader& blobLoader) mutable { if (auto optionalErrorCode = blobLoader.errorCode()) { promise->reject(Exception { *optionalErrorCode }); return; } auto arrayBuffer = blobLoader.arrayBufferResult(); if (!arrayBuffer) { promise->reject(Exception { InvalidStateError }); return; } promise->resolve(*arrayBuffer); }); } ExceptionOr> Blob::stream(ScriptExecutionContext& scriptExecutionContext) { class BlobStreamSource : public FileReaderLoaderClient, public ReadableStreamSource { public: BlobStreamSource(ScriptExecutionContext& scriptExecutionContext, Blob& blob) : m_loader(makeUniqueRef(FileReaderLoader::ReadType::ReadAsArrayBuffer, this)) { m_loader->start(&scriptExecutionContext, blob); } private: // ReadableStreamSource void setActive() final { } void setInactive() final { } void doStart() final { m_isStarted = true; if (m_exception) controller().error(*m_exception); } void doPull() final { } void doCancel() final { m_loader->cancel(); } // FileReaderLoaderClient void didStartLoading() final { } void didReceiveData() final { auto result = m_loader->arrayBufferResult(); if (!result) return; if (m_loader->isCompleted() && !m_bytesRead) controller().enqueue(WTFMove(result)); else { auto bytesLoaded = m_loader->bytesLoaded(); controller().enqueue(result->slice(m_bytesRead, bytesLoaded)); m_bytesRead = bytesLoaded; } } void didFinishLoading() final { controller().close(); } void didFail(ExceptionCode code) final { Exception exception { code }; if (!m_isStarted) { m_exception = WTFMove(exception); return; } controller().error(exception); } UniqueRef m_loader; size_t m_bytesRead { 0 }; bool m_isStarted { false }; std::optional m_exception; }; auto* globalObject = scriptExecutionContext.globalObject(); if (!globalObject) return Exception { InvalidStateError }; return ReadableStream::create(*globalObject, adoptRef(*new BlobStreamSource(scriptExecutionContext, *this))); } #if ASSERT_ENABLED bool Blob::isNormalizedContentType(const String& contentType) { // FIXME: Do we really want to treat the empty string and null string as valid content types? unsigned length = contentType.length(); for (size_t i = 0; i < length; ++i) { if (contentType[i] < 0x20 || contentType[i] > 0x7e) return false; if (isASCIIUpper(contentType[i])) return false; } return true; } bool Blob::isNormalizedContentType(const CString& contentType) { // FIXME: Do we really want to treat the empty string and null string as valid content types? size_t length = contentType.length(); const char* characters = contentType.data(); for (size_t i = 0; i < length; ++i) { if (characters[i] < 0x20 || characters[i] > 0x7e) return false; if (isASCIIUpper(characters[i])) return false; } return true; } #endif // ASSERT_ENABLED URLRegistry& Blob::registry() const { return BlobURLRegistry::registry(); } const char* Blob::activeDOMObjectName() const { return "Blob"; } BlobURLHandle Blob::handle() const { return BlobURLHandle { m_internalURL }; } } // namespace WebCore